Sensory examination Normal and pathology along with causes
| Pathway | Fibers carried | Route | Decussation |
|---|---|---|---|
| Posterior column - Medial lemniscus | Vibration, joint position sense (proprioception), discriminative touch, 2-point discrimination | Enters dorsal horn → ascends ipsilaterally in posterior column → crosses in medulla → medial lemniscus → VPL thalamus → parietal cortex | At medulla |
| Anterolateral (spinothalamic) | Pain, temperature, crude touch | Enters dorsal horn → crosses within 1-2 segments via anterior commissure → ascends contralaterally → VPL thalamus → parietal cortex | In spinal cord (1-2 levels above entry) |
| Test | Technique | Normal Threshold |
|---|---|---|
| Two-point discrimination | Compass applied simultaneously; minimum distance perceived as two distinct points | Fingertip: 3-5 mm; palm: 8-15 mm; dorsum hand/foot: 20-30 mm; trunk: 4-7 cm |
| Stereognosis | Patient identifies object by touch alone (coin, key, paperclip) with eyes closed | Correct identification |
| Graphesthesia | Number or letter written on skin surface | Correct identification |
| Double simultaneous stimulation (DSS) | Both hands touched simultaneously; patient identifies which side(s) touched | Both sides identified correctly |
| Point localization (topognosis) | Touch a point; patient points to it with eyes closed | Accurate localization |
| Finding | Normal | Abnormal |
|---|---|---|
| Light touch | Symmetric, accurate | Absent = anesthesia; Reduced = hypesthesia; Exaggerated = hyperesthesia |
| Pain | Symmetric sharp sensation | Absent = analgesia; Reduced = hypalgesia; Exaggerated = hyperalgesia |
| Temperature | Correct warm/cold distinction | Absent = thermoanesthesia; Inverted = rare |
| Vibration | Symmetric; felt as long as examiner | Lost distally first in neuropathy |
| Proprioception | 1-2 mm excursions correctly identified | Loss → sensory ataxia, positive Romberg |
| DSS | Both sides perceived | Extinction of one side → contralateral parietal lesion |
| Stereognosis | Objects identified correctly | Astereognosis → parietal/posterior column lesion |



| Level of Lesion | Pattern | Modalities Lost | Laterality |
|---|---|---|---|
| Single peripheral nerve | Specific nerve territory | All | Unilateral |
| Polyneuropathy | Glove and stocking | All (distally) | Bilateral |
| Nerve root | Dermatomal band | All | Unilateral |
| Ganglionopathy | Non-length-dependent, proximal | All | Can be bilateral/multifocal |
| Brown-Séquard | Ipsi: vibration/proprioception; Contra: pain/temp | Dissociated | Split |
| Syringomyelia (central cord) | Cape distribution, dissociated | Pain/temp (touch spared) | Bilateral |
| Anterior cord | Below level | Pain/temp (vibration spared) | Bilateral |
| Posterior column | Below level | Vibration/proprioception (pain spared) | Bilateral |
| Complete cord transection | Below level - sharp level | All | Bilateral |
| Lateral medulla | Ipsi face + contra body | Pain/temp | Alternating |
| Medial medulla | Contra body | Vibration/proprioception | Contralateral |
| Thalamus | Contra hemi (face+arm+leg) | All | Contralateral |
| Parietal cortex | Contra hemi | Cortical (discriminative) | Contralateral |
Provide the downloadable pdf of the same
mkdir -p /tmp/workspace/sensory-exam && ls /tmp/workspace/sensory-exam/
~/sensory-exam/generate_pdf.py
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm
from reportlab.lib import colors
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.platypus import ListFlowable, ListItem
OUTPUT = "/tmp/workspace/sensory-exam/Sensory_Examination.pdf"
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
rightMargin=2*cm, leftMargin=2*cm,
topMargin=2.2*cm, bottomMargin=2.2*cm,
title="Sensory Examination: Normal & Pathology",
author="Orris Medical"
)
styles = getSampleStyleSheet()
# ── Custom styles ────────────────────────────────────────────────────────────
NAVY = colors.HexColor("#1a3557")
TEAL = colors.HexColor("#1a7a7a")
LGRAY = colors.HexColor("#f0f4f8")
DGRAY = colors.HexColor("#4a4a4a")
ORANGE = colors.HexColor("#c45000")
WHITE = colors.white
title_style = ParagraphStyle("TitleStyle", parent=styles["Title"],
fontSize=22, textColor=WHITE, alignment=TA_CENTER,
spaceAfter=4, fontName="Helvetica-Bold", leading=28)
subtitle_style = ParagraphStyle("SubtitleStyle", parent=styles["Normal"],
fontSize=11, textColor=colors.HexColor("#cce0ff"), alignment=TA_CENTER,
spaceAfter=2, fontName="Helvetica", leading=14)
h1_style = ParagraphStyle("H1", parent=styles["Heading1"],
fontSize=14, textColor=WHITE, fontName="Helvetica-Bold",
spaceBefore=12, spaceAfter=4, leading=18,
backColor=NAVY, leftIndent=-0.5*cm, rightIndent=-0.5*cm,
borderPad=6)
h2_style = ParagraphStyle("H2", parent=styles["Heading2"],
fontSize=12, textColor=NAVY, fontName="Helvetica-Bold",
spaceBefore=8, spaceAfter=3, leading=16,
borderPadding=(0,0,2,0))
h3_style = ParagraphStyle("H3", parent=styles["Heading3"],
fontSize=11, textColor=TEAL, fontName="Helvetica-BoldOblique",
spaceBefore=6, spaceAfter=2, leading=14)
body_style = ParagraphStyle("Body", parent=styles["Normal"],
fontSize=9.5, textColor=DGRAY, fontName="Helvetica",
spaceBefore=2, spaceAfter=2, leading=13, alignment=TA_JUSTIFY)
bullet_style = ParagraphStyle("Bullet", parent=body_style,
leftIndent=14, bulletIndent=4, spaceBefore=1, spaceAfter=1)
small_style = ParagraphStyle("Small", parent=body_style,
fontSize=8, textColor=colors.HexColor("#666666"), fontName="Helvetica-Oblique")
bold_inline = '<font name="Helvetica-Bold">'
def H1(txt):
return Paragraph(f" {txt}", h1_style)
def H2(txt):
return Paragraph(txt, h2_style)
def H3(txt):
return Paragraph(txt, h3_style)
def P(txt):
return Paragraph(txt, body_style)
def B(txt):
return Paragraph(f"<bullet>•</bullet> {txt}", bullet_style)
def SP(n=6):
return Spacer(1, n)
def HR():
return HRFlowable(width="100%", thickness=0.5, color=colors.HexColor("#cccccc"), spaceAfter=4, spaceBefore=4)
def tbl_style(header_color=NAVY):
return TableStyle([
('BACKGROUND', (0,0), (-1,0), header_color),
('TEXTCOLOR', (0,0), (-1,0), WHITE),
('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
('FONTSIZE', (0,0), (-1,0), 8.5),
('ROWBACKGROUNDS',(0,1),(-1,-1),[WHITE, LGRAY]),
('FONTNAME', (0,1), (-1,-1), 'Helvetica'),
('FONTSIZE', (0,1), (-1,-1), 8),
('TEXTCOLOR', (0,1), (-1,-1), DGRAY),
('GRID', (0,0), (-1,-1), 0.4, colors.HexColor("#bbbbbb")),
('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
('TOPPADDING', (0,0), (-1,-1), 4),
('BOTTOMPADDING',(0,0), (-1,-1), 4),
('LEFTPADDING', (0,0), (-1,-1), 6),
('RIGHTPADDING', (0,0), (-1,-1), 6),
('WORDWRAP', (0,0), (-1,-1), True),
])
# ─────────────────────────── TITLE BLOCK ─────────────────────────────────────
def title_block():
data = [[Paragraph("SENSORY EXAMINATION", title_style)],
[Paragraph("Normal Findings · Pathological Patterns · Causes · Localisation", subtitle_style)],
[Paragraph("Sources: Adams & Victor's Principles of Neurology 12e | Harrison's Internal Medicine 22e | Neuroanatomy through Clinical Cases 3e", subtitle_style)]]
t = Table(data, colWidths=[17*cm])
t.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,-1), NAVY),
('TOPPADDING', (0,0), (-1,-1), 8),
('BOTTOMPADDING', (0,0), (-1,-1), 8),
('LEFTPADDING', (0,0), (-1,-1), 12),
('RIGHTPADDING', (0,0), (-1,-1), 12),
('ROUNDEDCORNERS',[6]),
]))
return t
# ───────────────────────── CONTENT ───────────────────────────────────────────
story = []
story.append(title_block())
story.append(SP(14))
# ══════════════════════════════════════════════════════════════════════════════
# SECTION 1: Anatomical Basis
# ══════════════════════════════════════════════════════════════════════════════
story.append(H1("1. Anatomical Basis — The Two Major Sensory Pathways"))
story.append(SP(4))
story.append(P("<b>Two parallel pathways carry distinct sensory modalities from periphery to cortex. Their anatomical separation is the key to understanding dissociated sensory loss patterns.</b>"))
story.append(SP(6))
pathway_data = [
["Pathway", "Fibers Carried", "Route", "Where it Crosses"],
["Posterior Column–\nMedial Lemniscus",
"Vibration, joint position (proprioception),\ndiscriminative touch,\ntwo-point discrimination",
"Enters dorsal horn → ascends IPSILATERALLY in posterior column (fasciculus gracilis/cuneatus) → decussates in medulla → medial lemniscus → VPL thalamus → parietal cortex (S1)",
"Medulla (internal arcuate fibers)"],
["Anterolateral\n(Spinothalamic)",
"Pain, temperature,\ncrude touch, itch",
"Enters dorsal horn (Rexed laminae I, IV–V) → crosses via ANTERIOR COMMISSURE within 1–2 segments → ascends contralaterally → VPL thalamus → parietal cortex",
"Spinal cord (1–2 levels above entry)"],
]
pt = Table(pathway_data, colWidths=[3.2*cm, 4.5*cm, 6.3*cm, 3*cm])
pt.setStyle(tbl_style(NAVY))
story.append(pt)
story.append(SP(6))
story.append(H2("Fiber Types and Their Sensory Roles"))
fiber_data = [
["Fiber Type", "Myelination", "Conduction", "Sensation Carried"],
["A-α (Group Ia/II)", "Heavily myelinated", "70–120 m/s", "Touch, pressure, vibration, proprioception"],
["A-δ (Group III)", "Lightly myelinated", "5–30 m/s", "Sharp/fast pain, cold temperature"],
["C (Group IV)", "Unmyelinated", "0.5–2 m/s", "Slow/burning pain, warmth, itch, autonomic"],
]
ft = Table(fiber_data, colWidths=[3.2*cm, 3.5*cm, 3.5*cm, 6.8*cm])
ft.setStyle(tbl_style(TEAL))
story.append(ft)
story.append(SP(4))
# ══════════════════════════════════════════════════════════════════════════════
# SECTION 2: Modalities and Normal Technique
# ══════════════════════════════════════════════════════════════════════════════
story.append(PageBreak())
story.append(H1("2. Sensory Modalities Tested — Normal Technique"))
story.append(SP(4))
story.append(H2("A. Primary Modalities"))
story.append(H3("i. Light Touch"))
story.append(B("Tool: Wisp of cotton or examiner's fingertip"))
story.append(B("Technique: Single gentle touches; patient says 'yes' with eyes closed"))
story.append(B("Normal: Detected equally bilaterally; moving stimulus more sensitive than stationary"))
story.append(B("Von Frey hair used for quantitative threshold testing in research"))
story.append(SP(4))
story.append(H3("ii. Pain (Superficial)"))
story.append(B("Tool: New, disposable sharp pin"))
story.append(B("Technique: Ask patient to report 'sharpness', not mere contact; ~1 stimulus/second; not over the same spot (summation causes false hyperalgesia)"))
story.append(B("Useful comparison: ask patient to rate sensation on 0–10 scale in two areas simultaneously"))
story.append(B("Abnormal area boundary: demarcate by moving from numb → normal region"))
story.append(SP(4))
story.append(H3("iii. Temperature"))
story.append(B("Tool: Tuning fork (one side rubbed warm, apply alternating sides); or test tubes with warm (40–45°C) and cold (5–10°C) water"))
story.append(B("Technique: Binary choice — 'warm or cold?' Useful for corroborating a zone of hypalgesia"))
story.append(B("Normal: Correct identification of warm vs. cold everywhere"))
story.append(B("Clinical note: Cold carried by A-δ fibers; warmth by C fibers — differentially affected in small-fiber neuropathies"))
story.append(SP(4))
story.append(H3("iv. Vibration"))
story.append(B("Tool: 128 Hz tuning fork applied to distal bony prominences"))
story.append(B("Sites tested (distal → proximal): Great toe → medial malleolus → patella → anterior iliac crest; Index finger → wrist → elbow → shoulder"))
story.append(B("Technique: Compare patient's perception duration to examiner's own; quantitative tuning forks (0–8 scale) available"))
story.append(B("Normal: Symmetric bilaterally; perceived at least as long as the examiner"))
story.append(B("<b>Key pattern:</b> Loss at ankle but preserved at knee → length-dependent peripheral neuropathy"))
story.append(B("<b>Key pattern:</b> Loss at iliac crest level → spinal cord lesion at that level"))
story.append(B("Age effect: Mildly reduced vibration at great toe is normal in elderly — do not over-interpret"))
story.append(SP(4))
story.append(H3("v. Joint Position Sense (Proprioception)"))
story.append(B("Technique: Grasp digit LATERALLY (not dorsally — to avoid pressure cues); move 1–2 mm up or down; patient reports direction with eyes closed"))
story.append(B("Start distally (distal phalanx of great toe or index finger); move proximally if abnormal"))
story.append(B("Normal: Even 1–2 mm excursions correctly identified"))
story.append(SP(4))
story.append(H3("vi. Romberg Test (Proprioception)"))
story.append(B("Method: Patient stands, feet as close together as needed for balance, eyes open; then closes eyes"))
story.append(B("<b>Positive Romberg</b>: Loses balance on eye closure = proprioceptive loss (NOT cerebellar)"))
story.append(B("Cerebellar patients are unsteady with eyes OPEN; proprioceptive loss is unmasked only by removing visual compensation"))
story.append(SP(8))
story.append(H2("B. Cortical / Discriminative Sensation"))
story.append(P("<i>Only meaningful when primary modalities are confirmed to be intact. Requires intact parietal cortex integration.</i>"))
story.append(SP(4))
cortical_data = [
["Test", "Technique", "Normal", "Abnormality Name"],
["Two-point discrimination",
"Compass applied simultaneously; find minimum distance perceived as two distinct points",
"Fingertip: 3–5 mm\nPalm: 8–15 mm\nDorsum hand/foot: 20–30 mm\nTrunk: 4–7 cm",
"Widened threshold"],
["Stereognosis",
"Patient identifies object by touch alone (coin, key, paperclip) — eyes closed",
"Correct identification",
"Astereognosis"],
["Graphesthesia",
"Examiner writes number or letter on skin; patient identifies",
"Correct identification",
"Agraphesthesia"],
["Double Simultaneous Stimulation (DSS)",
"Touch both hands simultaneously; patient identifies which side(s) touched",
"Both sides perceived",
"Extinction of contralateral side"],
["Point Localization (Topognosis)",
"Touch a point; patient points to it with eyes closed",
"Accurate localization",
"Topagnosia"],
]
ct = Table(cortical_data, colWidths=[3.5*cm, 4.5*cm, 3.5*cm, 3.5*cm])
ct.setStyle(tbl_style(TEAL))
story.append(ct)
story.append(SP(8))
# ══════════════════════════════════════════════════════════════════════════════
# SECTION 3: Normal vs Abnormal Findings
# ══════════════════════════════════════════════════════════════════════════════
story.append(PageBreak())
story.append(H1("3. Normal vs. Abnormal Findings"))
story.append(SP(4))
story.append(H2("A. Quantitative Terms for Sensory Findings"))
quant_data = [
["Prefix/Term", "Meaning"],
["Anesthesia / Analgesia / Thermoanesthesia", "Complete absence of touch / pain / temperature"],
["Hypesthesia / Hypalgesia", "Reduced sensation of touch / pain"],
["Hyperesthesia / Hyperalgesia", "Exaggerated perception of touch / pain"],
["Allodynia", "Pain produced by a normally non-painful stimulus (e.g., light touch causes pain)"],
["Dysesthesia", "Unpleasant abnormal sensation (spontaneous or evoked)"],
["Paresthesia", "Spontaneous tingling, pins-and-needles (ectopic discharge in large fibers)"],
["Hyperpathia", "Exaggerated pain response with raised threshold + persistence/spread after stimulus"],
["Anesthesia dolorosa", "Pain in an area that is objectively numb (e.g., post-nerve injury)"],
["Dissociated sensory loss", "Loss of one modality (e.g., pain/temperature) with preservation of another (e.g., touch/vibration) — hallmark of syringomyelia or anterior cord syndrome"],
]
qt = Table(quant_data, colWidths=[7*cm, 10*cm])
qt.setStyle(tbl_style(ORANGE))
story.append(qt)
story.append(SP(8))
story.append(H2("B. Sensory Symptoms and Their Fiber Basis"))
fiber_sx = [
["Symptom", "Fiber Type Involved", "Example Cause"],
["Tingling / buzzing paresthesia", "Large myelinated (A-α); posterior columns", "Nerve compression, B12 deficiency, MS"],
["Burning pain", "C fibers (unmyelinated)", "Small-fiber neuropathy, diabetes, CRPS"],
["Sharp / lancinating pain", "A-δ fibers", "Tabes dorsalis, root compression"],
["Band-like sensation on trunk/limb", "Large fibers — posterior column disease", "MS, cervical spondylosis, B12 deficiency"],
["Pseudocramp (no actual contraction)","Muscle proprioceptors", "Posterior column disease"],
["Hot/cold reversal (allodynia to temp)","A-δ + C fibers", "Ciguatera poisoning, oxaliplatin neuropathy"],
["Vibratory paresthesias", "Posterior column / cerebral disease", "MS, thalamic lesion (rare)"],
["Lhermitte's sign (electric shock on neck flexion)", "Posterior columns (cervical)", "MS, cervical cord compression, subacute combined degeneration"],
]
fs = Table(fiber_sx, colWidths=[4.5*cm, 4.5*cm, 8*cm])
fs.setStyle(tbl_style(NAVY))
story.append(fs)
# ══════════════════════════════════════════════════════════════════════════════
# SECTION 4: Pathological Patterns and Causes
# ══════════════════════════════════════════════════════════════════════════════
story.append(PageBreak())
story.append(H1("4. Pathological Patterns and Their Causes"))
story.append(SP(4))
story.append(H2("A. Peripheral Nerve (Mononeuropathy)"))
story.append(P("<b>Pattern:</b> Sensory loss in the specific anatomical territory of ONE nerve. All modalities affected within that territory. Associated with LMN-type weakness and reduced reflex in that distribution."))
story.append(SP(3))
story.append(B("<b>Median nerve</b>: Lateral 3½ fingers (thumb, index, middle, half ring) + thenar eminence — carpal tunnel syndrome (commonest entrapment), trauma"))
story.append(B("<b>Ulnar nerve</b>: Medial 1½ fingers (little + half ring) + hypothenar — cubital tunnel, leprosy"))
story.append(B("<b>Radial nerve</b>: Dorsal lateral hand/wrist drop — 'Saturday night palsy' (compression in spiral groove)"))
story.append(B("<b>Common peroneal nerve</b>: Dorsum foot + lateral leg — foot drop — fibular head compression"))
story.append(B("<b>Lateral femoral cutaneous nerve</b>: Lateral thigh burning/numbness — meralgia paresthetica"))
story.append(B("<b>Causes:</b> Compression, trauma, diabetes, vasculitis, leprosy (cooler skin areas selectively affected), infiltration (lymphoma, amyloid)"))
story.append(SP(6))
story.append(H2("B. Polyneuropathy (Peripheral Neuropathy)"))
story.append(P("<b>Pattern:</b> Bilateral symmetric 'glove and stocking' distribution — distal-to-proximal gradient (length-dependent). Longest fibers fail first. Vibration lost first at great toes; ankle jerks lost first."))
story.append(SP(3))
poly_causes = [
["Category", "Causes"],
["Metabolic / Endocrine", "Diabetes mellitus (commonest worldwide), hypothyroidism, uraemia (renal failure), hepatic failure, porphyria"],
["Nutritional / Toxic", "Vitamin B12 deficiency, thiamine (B1) deficiency, alcohol excess, pyridoxine (B6) excess, heavy metals (lead, arsenic, thallium)"],
["Drugs", "Vincristine, cisplatin, taxanes, metronidazole, isoniazid (without B6), amiodarone, nitrofurantoin"],
["Inflammatory / Immune", "Guillain-Barré syndrome (acute demyelinating), CIDP (chronic), multifocal motor neuropathy, vasculitis, sarcoidosis"],
["Hereditary", "Charcot-Marie-Tooth (CMT) — commonest hereditary neuropathy; hereditary amyloidosis (TTR)"],
["Infection", "HIV, leprosy, Lyme disease, diphtheria"],
["Paraprotein / Infiltrative", "Monoclonal gammopathy (MGUS, myeloma), amyloidosis (AL), cryoglobulinaemia"],
["Paraneoplastic", "Anti-Hu (ANNA-1) antibody — small cell lung carcinoma; sensory neuronopathy"],
]
pct = Table(poly_causes, colWidths=[4.5*cm, 12.5*cm])
pct.setStyle(tbl_style(TEAL))
story.append(pct)
story.append(SP(6))
story.append(H2("C. Radiculopathy (Nerve Root)"))
story.append(P("<b>Pattern:</b> Dermatomal band or strip of sensory loss. Unilateral. Radicular pain radiates from spine. Reflex loss limited to affected root."))
story.append(SP(3))
story.append(B("Lumbar: L4 — medial leg/foot, knee jerk reduced; L5 — dorsum foot/big toe, no specific reflex; S1 — lateral foot/sole, ankle jerk reduced"))
story.append(B("Cervical: C5 — shoulder/lateral arm; C6 — thumb/index, biceps jerk reduced; C7 — middle finger, triceps jerk reduced; C8 — ring/little fingers"))
story.append(B("<b>Causes:</b> Intervertebral disc herniation (L4/5/S1 lumbar; C5/6/7 cervical), spondylosis/foraminal stenosis, herpes zoster (also produces vesicles in that dermatome), tumour, epidural abscess, arachnoiditis"))
story.append(SP(6))
story.append(H2("D. Dorsal Root Ganglionopathy (Sensory Neuronopathy)"))
story.append(P("<b>Pattern:</b> Non-length-dependent — proximal areas ALSO affected early. ALL modalities affected (cell body destroyed). Prominent sensory ataxia; Romberg positive; areflexia. May be asymmetric, multifocal (pseudoataxic)."))
story.append(SP(3))
story.append(B("<b>Paraneoplastic:</b> Anti-Hu antibody (ANNA-1) — small cell lung cancer"))
story.append(B("<b>Sjögren's syndrome:</b> Immune-mediated destruction of dorsal root ganglia"))
story.append(B("<b>Drugs:</b> Cisplatin, carboplatin, pyridoxine (B6) toxicity"))
story.append(B("<b>Infectious:</b> Varicella-zoster virus ganglionitis"))
story.append(B("<b>Idiopathic</b>"))
story.append(SP(8))
# ══════════════════════════════════════════════════════════════════════════════
# SECTION 5: Spinal Cord Syndromes
# ══════════════════════════════════════════════════════════════════════════════
story.append(PageBreak())
story.append(H1("5. Spinal Cord Sensory Syndromes"))
story.append(SP(4))
cord_data = [
["Syndrome", "Lesion Site", "Sensory Pattern", "Other Features", "Common Causes"],
["Complete Transverse\nMyelopathy",
"All tracts at one level",
"ALL modalities lost BELOW level\n(sharp sensory level on trunk;\nhyperaesthesia at upper margin)",
"UMN weakness below level\nBladder/bowel dysfunction\nAutonomic disturbance",
"Trauma, MS, transverse myelitis,\ntumour compression, epidural abscess,\nhaematoma, cord infarction"],
["Brown-Séquard\n(Hemisection)",
"Ipsilateral half-cord",
"IPSILATERAL: loss of vibration +\nproprioception (posterior column)\nCONTRALATERAL: loss of pain +\ntemperature, 2–3 levels below\n(spinothalamic — already crossed)",
"Ipsilateral UMN weakness\nbelow lesion",
"Stab wound (classic), MS,\ntumour, radiation, disc herniation"],
["Syringomyelic\n(Central Cord)",
"Anterior commissure\n(crossing spinothalamic fibers)",
"BILATERAL loss of pain +\ntemperature in 'cape' distribution\n(neck, shoulders, arms)\nTouch + proprioception PRESERVED\n= DISSOCIATED sensory loss",
"Segmental amyotrophy\nReflex loss in affected segments\nLater: long tract signs",
"Syringomyelia (commonest cervical),\nintramedullary tumour (ependymoma,\nastrocytoma), trauma, haemorrhage"],
["Posterior Column",
"Dorsal columns\n(fasciculus gracilis/cuneatus)",
"BILATERAL loss of vibration +\nproprioception below level\nPain/temperature SPARED\nSensory ataxia; Romberg positive\nParaesthesias; Lhermitte's sign",
"Tendon reflexes PRESERVED\n(unlike tabetic — key distinction)",
"MS (most common), subacute combined\ndegeneration (B12 deficiency),\ncervical spondylotic myelopathy,\nFriedreich's ataxia"],
["Tabetic Syndrome",
"Posterior roots +\nposterior columns",
"Same as posterior column but\nAREFLEXIA (roots involved)\nLightning/lancinating pains\nCharcot joints; Romberg positive",
"Areflexia (distinguishes from\npure posterior column lesion)",
"Tabes dorsalis (neurosyphilis),\ndiabetes, paraneoplastic"],
["Anterior Spinal\nArtery Syndrome",
"Anterior 2/3 of cord\n(posterior columns spared —\nsupplied by posterior\nspinal arteries)",
"BILATERAL loss of pain +\ntemperature below level\nVibration + proprioception\nPRESERVED",
"UMN weakness below level\nBladder/bowel dysfunction\nOnset sudden (vascular)",
"Aortic surgery/aneurysm,\nthromboembolism, atherosclerosis,\nhypotension, anterior disc prolapse"],
]
cord_t = Table(cord_data, colWidths=[2.8*cm, 2.8*cm, 4.2*cm, 3.5*cm, 3.7*cm])
cord_t.setStyle(tbl_style(NAVY))
story.append(cord_t)
story.append(SP(8))
# ══════════════════════════════════════════════════════════════════════════════
# SECTION 6: Brainstem
# ══════════════════════════════════════════════════════════════════════════════
story.append(H1("6. Brainstem Sensory Syndromes"))
story.append(SP(4))
bs_data = [
["Syndrome", "Lesion", "Sensory Pattern", "Associated Features", "Causes"],
["Lateral Medullary\n(Wallenberg) Syndrome",
"Lateral medulla — spinothalamic tract + spinal trigeminal nucleus/tract",
"ALTERNATING pattern:\nIPSILATERAL FACE: pain + temp loss\n(spinal trigeminal nucleus)\nCONTRALATERAL BODY: pain + temp loss\n(spinothalamic tract)\nVibration/proprioception SPARED",
"Ipsilateral Horner's syndrome\nDysphagia, hoarseness (CN IX, X)\nIPSILATERAL ataxia (cerebellar)\nVertigo, nystagmus (vestibular)\nHiccups",
"PICA occlusion (most common)\nVertebral artery atherosclerosis\nor dissection"],
["Medial Medullary\nSyndrome",
"Medial medulla — medial lemniscus + pyramid + CN XII",
"CONTRALATERAL BODY:\nVibration + proprioception loss\n(medial lemniscus)\nPain/temperature relatively spared",
"Ipsilateral tongue deviation\n(CN XII palsy)\nContralateral hemiplegia\n(pyramidal)",
"Anterior spinal artery branch\nocclusion; vertebral artery\nthrombosis"],
["Pontine Lesion\n(Lateral Pons)",
"Lateral pons — spinothalamic + ipsilateral trigeminal",
"Same alternating pattern as lateral medullary — ipsilateral facial + contralateral body pain/temp loss",
"Ipsilateral CN VI, VII palsy\nAtaxia, nystagmus",
"Infarction, MS, tumour"],
["Midbrain / Upper Pons",
"Less common — contralateral somatosensory deficits including face, arm and leg",
"Contralateral hemisensory loss (all modalities — face + body)",
"Associated motor, CN III/IV palsy depending on exact location",
"Infarction, tumour, MS"],
]
bs_t = Table(bs_data, colWidths=[3*cm, 3.5*cm, 4.5*cm, 3*cm, 3*cm])
bs_t.setStyle(tbl_style(TEAL))
story.append(bs_t)
story.append(SP(8))
# ══════════════════════════════════════════════════════════════════════════════
# SECTION 7: Thalamus and Cortex
# ══════════════════════════════════════════════════════════════════════════════
story.append(PageBreak())
story.append(H1("7. Thalamic and Cortical Lesions"))
story.append(SP(4))
story.append(H2("A. Thalamic Lesions (VPL / VPM Nuclei)"))
story.append(P("<b>Pattern:</b> Contralateral hemisensory loss — face + arm + leg (all modalities). Face, hand (especially lips and fingertips), and foot more severely affected than trunk. Sometimes present without motor deficit (pure sensory stroke — lacunar)."))
story.append(SP(4))
story.append(P("<b>Dejerine-Roussy Syndrome (Thalamic Pain Syndrome):</b> After partial thalamic recovery, spontaneous burning/agonizing pain develops on the affected side. Paradox: elevated pain threshold (stronger stimulus needed to evoke pain — hypalgesia) combined with exaggerated, lingering response to stimuli that do get through (hyperpathia). Cold stimuli, emotion, loud noise, even music may aggravate. One of the most distressing pain syndromes in neurology."))
story.append(SP(4))
story.append(B("<b>Causes:</b> Thalamic infarction (lacunar — most common), thalamic haemorrhage, tumour, MS, arteriovenous malformation"))
story.append(SP(8))
story.append(H2("B. Parietal Lobe (Cortical) Lesions"))
story.append(P("<b>Primary modalities are often relatively PRESERVED</b> (unless lesion is extensive). The hallmark is loss of <b>discriminative (cortical) sensation</b>:"))
story.append(SP(3))
cortical_syndromes = [
["Syndrome / Feature", "Pattern", "Lesion Location", "Causes"],
["Verger-Dejerine Syndrome\n(Anterior Parietal)",
"Discriminative loss predominates: astereognosis, agraphesthesia, widened 2-point threshold, topagnosia, impaired position sense.\nPrimary modalities relatively preserved.",
"Postcentral gyrus (S1)\nAnterior parietal lobe",
"Stroke (MCA), tumour, trauma, MS"],
["Dejerine-Mouzon Syndrome\n(Posterior Parietal)",
"SEVERE impairment of primary modalities (pain, thermal, tactile) — simulates thalamic lesion.\nVibratory sense relatively spared.\nMotor variable.",
"Posterior parietal lobe\n(more extensive/deep lesion)",
"Stroke, tumour"],
["Sensory Extinction / Inattention",
"With bilateral simultaneous stimulation — contralateral side EXTINGUISHES.\nEach side perceived correctly when tested alone.\nSensory neglect (especially non-dominant hemisphere lesion).",
"Parietal lobe (often\nnon-dominant hemisphere)",
"Stroke, tumour, TBI"],
["Cortical Sensory Loss\n(Pure)",
"All primary modalities intact.\nAstereognosis, agraphesthesia, impaired DSS only.",
"Isolated parietal cortex S1",
"Small cortical infarct, focal MS"],
]
cs_t = Table(cortical_syndromes, colWidths=[3.5*cm, 5.5*cm, 3.5*cm, 4.5*cm])
cs_t.setStyle(tbl_style(ORANGE))
story.append(cs_t)
story.append(SP(8))
# ══════════════════════════════════════════════════════════════════════════════
# SECTION 8: Master Localisation Table
# ══════════════════════════════════════════════════════════════════════════════
story.append(PageBreak())
story.append(H1("8. Master Localisation Summary Table"))
story.append(SP(4))
master_data = [
["Level of Lesion", "Sensory Pattern", "Modalities Lost", "Side Affected", "Key Associated Features"],
["Single Peripheral Nerve\n(Mononeuropathy)",
"Specific nerve territory",
"All modalities in territory",
"Unilateral",
"LMN weakness; reflex loss in that nerve's distribution"],
["Polyneuropathy",
"Glove and stocking\n(bilateral, distal > proximal)",
"All; may be preferential\n(large fiber or small fiber)",
"Bilateral, symmetric",
"Hyporeflexia/areflexia (ankle jerks first); length-dependent vibration loss"],
["Nerve Root\n(Radiculopathy)",
"Dermatomal band",
"All in dermatome",
"Unilateral",
"Radicular pain; reflex loss at that root level"],
["Ganglionopathy",
"Non-length-dependent;\nproximal areas also affected",
"All modalities",
"Bilateral or multifocal",
"Prominent sensory ataxia; areflexia; no motor weakness"],
["Brown-Séquard\n(Hemisection)",
"Ipsi: vibration/proprioception\nContra: pain/temp (2–3 levels below)",
"Dissociated (split)",
"Crossed (split left-right)",
"Ipsilateral UMN weakness; ipsilateral posterior column + contralateral spinothalamic"],
["Syringomyelia\n(Central Cord)",
"Cape/shawl distribution\nBilateral pain/temp loss",
"Pain + temperature\n(Touch SPARED = dissociated)",
"Bilateral",
"Segmental amyotrophy; dissociated sensory loss is hallmark"],
["Anterior Cord Syndrome",
"Below level\nBilateral pain/temp loss",
"Pain + temperature\n(Vibration/proprioception SPARED)",
"Bilateral",
"Sudden onset; bilateral UMN weakness; bladder/bowel loss"],
["Posterior Column",
"Below level",
"Vibration + proprioception\n(Pain/temp SPARED)",
"Bilateral",
"Sensory ataxia; Romberg positive; Lhermitte's sign; reflexes preserved"],
["Complete Cord Transection",
"Sharp level on trunk —\nall below level",
"ALL modalities",
"Bilateral",
"Spinal shock; UMN signs below; autonomic dysfunction"],
["Lateral Medulla\n(Wallenberg)",
"Alternating: ipsi face +\ncontra body",
"Pain + temperature\n(Vibration/proprioception SPARED)",
"Crossed / alternating",
"Ipsilateral Horner's, dysphagia, ataxia, vertigo"],
["Medial Medulla",
"Contra body",
"Vibration + proprioception\n(Pain/temp SPARED)",
"Contralateral",
"Ipsilateral CN XII palsy; contralateral hemiplegia"],
["Thalamus (VPL/VPM)",
"Contralateral hemisphere\n(face + arm + leg)",
"ALL modalities\n(position sense most affected)",
"Contralateral",
"Dejerine-Roussy pain syndrome; may occur without hemiparesis (pure sensory stroke)"],
["Parietal Cortex (S1)",
"Contralateral hemisphere\nDiscriminative loss predominates",
"Cortical sensory loss\n(stereognosis, graphesthesia, DSS, 2-point)",
"Contralateral",
"Sensory extinction; neglect (non-dominant); primary modalities relatively preserved"],
]
master_t = Table(master_data, colWidths=[3*cm, 3.5*cm, 3.5*cm, 2.5*cm, 4.5*cm])
master_t.setStyle(tbl_style(NAVY))
story.append(master_t)
# ══════════════════════════════════════════════════════════════════════════════
# SECTION 9: Practical Examination Tips
# ══════════════════════════════════════════════════════════════════════════════
story.append(PageBreak())
story.append(H1("9. Practical Examination Tips & Pitfalls"))
story.append(SP(6))
tips = [
("<b>Test distally before proximally</b> — most pathology starts distally; start at feet, move upward.", NAVY),
("<b>Demarcate from abnormal toward normal</b> — you will detect the boundary of sensory loss more precisely moving from numb toward normal area.", TEAL),
("<b>Romberg vs. cerebellar:</b> Romberg tests proprioception, not cerebellar function. Cerebellar patients are unsteady with eyes OPEN. Romberg is positive only when vision is removed (proprioceptive loss).", NAVY),
("<b>Cortical tests require intact primary sensation</b> — stereognosis, graphesthesia, and DSS are only interpretable if touch is intact first.", TEAL),
("<b>Age effect on vibration:</b> Mildly reduced vibration at the great toe/ankle is normal in elderly patients due to age-related large fiber loss. Compare symmetry and compare with knees before concluding neuropathy.", NAVY),
("<b>Double simultaneous stimulation</b> is an excellent quick screen for parietal lesion — patient may miss contralateral side when tested simultaneously even if they detect it correctly when tested alone (extinction).", TEAL),
("<b>Functional (non-organic) sensory loss patterns:</b> Exact midline splitting (organic lesions don't split the midline — dermatomes overlap); non-dermatomal distribution; inconsistent on repetition; patient says 'no' to stimulus (instead of 'I can't feel it').", ORANGE),
("<b>Sensory level on trunk = spinal cord lesion</b> — demarcate using pin and vibration over dorsal vertebral spines + iliac crests. The actual cord lesion is typically 1–2 segments ABOVE the sensory level.", NAVY),
("<b>Dissociated sensory loss</b> (pain/temp lost, touch preserved) = either syringomyelia (central cord) or anterior cord syndrome. The opposite (touch lost, pain/temp preserved) = posterior cord lesion.", TEAL),
("<b>Glove-and-stocking + areflexia</b> = peripheral neuropathy until proven otherwise.", ORANGE),
]
for txt, col in tips:
tip_t = Table([[Paragraph(txt, ParagraphStyle("tip", parent=body_style, fontSize=9.5, leading=13))]],
colWidths=[17*cm])
tip_t.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,-1), colors.HexColor("#f8f9fa")),
('LEFTPADDING', (0,0), (-1,-1), 10),
('RIGHTPADDING', (0,0), (-1,-1), 10),
('TOPPADDING', (0,0), (-1,-1), 6),
('BOTTOMPADDING', (0,0), (-1,-1), 6),
('LINEAFTER', (0,0), (0,-1), 4, col),
('LINEBEFORE', (0,0), (0,-1), 4, col),
('ROUNDEDCORNERS', [4]),
]))
story.append(tip_t)
story.append(SP(5))
story.append(SP(10))
HR2 = HRFlowable(width="100%", thickness=1, color=NAVY, spaceAfter=6, spaceBefore=6)
story.append(HR2)
story.append(Paragraph(
"Sources: Adams & Victor's Principles of Neurology 12e (pp. 174–184) | Harrison's Principles of Internal Medicine 22e (p. 3425) | Neuroanatomy through Clinical Cases 3e (pp. 314–315)",
small_style))
story.append(Paragraph("Generated by Orris Medical AI | July 2026", small_style))
# ─────────────────────────── BUILD ───────────────────────────────────────────
doc.build(story)
print(f"PDF saved: {OUTPUT}")
cd /tmp/workspace/sensory-exam && python generate_pdf.py 2>&1
ls -lh /tmp/workspace/sensory-exam/Sensory_Examination.pdf
Sensory Examination
PDF Document · PDF