7th nerve pg ophthalmology question reference from Kanski Ak Khurana dukes
Library note: Kanski's Clinical Ophthalmology (10th ed.) is available in full. AK Khurana and Duke-Elder are not in this library, so those sections are covered from standard knowledge below, clearly marked.

| Feature | Detail |
|---|---|
| Central innervation of upper face | Bilateral (from both hemispheres) |
| Central innervation of lower face | Unilateral (contralateral only) |
| Supranuclear lesion | Lower face palsy on opposite side; forehead spared |
| Nuclear / Peripheral lesion | Complete ipsilateral palsy (upper + lower face) |
| Level | Signs |
|---|---|
| Pons | Ipsilateral facial paralysis + 6th nerve palsy + contralateral hemiplegia + paralysis of conjugate gaze to lesion side (PPRF) |
| Cerebellopontine angle | Ipsilateral facial palsy + decreased tears + decreased saliva + hyperacusis + loss of taste (anterior 2/3 tongue) |
| With 8th nerve | Above + deafness + tinnitus |
| Parotid gland | Ipsilateral facial palsy + numbness + expressionless face |
| Ramsay Hunt syndrome | Geniculate ganglion HZV reactivation → ipsilateral facial palsy + ear pain + vesicles on face/ear |

| Cause | Frequency |
|---|---|
| Bell palsy (idiopathic) | 60-70% |
| Trauma | 10% |
| Viral (HSV most common; also CMV, EBV, mumps, rubella) | 5% |
| Lyme disease | Often bilateral |
| Neoplastic (parotid malignancy, vestibular schwannoma) | ~2% |
| Diabetes | - |
| Sarcoidosis | Often bilateral |
TIP (Kanski): If onset of facial nerve palsy is slow or other neurological signs are present, investigate with MRI to exclude intracranial pathology.
| Point | Fact |
|---|---|
| 7th nerve branches | Two Tom Zulus Beat Cadets (Temporal, Zygomatic, Buccal, Mandibular, Cervical) |
| Nerve to lacrimal gland | Greater petrosal nerve (branch of VII at geniculate ganglion) |
| Facial colliculus | VII nerve fibres loop around VI nucleus - pontine lesion = VI + VII palsy |
| UMN facial palsy | Forehead SPARED (bilateral cortical innervation to frontalis) |
| LMN facial palsy | Forehead INVOLVED (complete ipsilateral palsy) - Bell's palsy |
| Bell's phenomenon | Upward + outward eye movement on attempted closure (protective) |
| Crocodile tears | Aberrant regeneration of parasympathetic fibres → Bogorad syndrome |
| Ramsay Hunt syndrome | HZV in geniculate ganglion |
| Corneal reflex - afferent | V1 (ophthalmic division of trigeminal) |
| Corneal reflex - efferent | VII (facial) |
| Bell palsy treatment | Prednisolone 60-80 mg/day started within 48-72 hrs + antivirals |
| Bilateral facial palsy causes | Sarcoidosis, Lyme disease, Guillain-Barre, bilateral parotid tumours |
| Schirmer test uses in VII palsy | Tests lacrimal secretion - useful to localise lesion (proximal/distal to geniculate ganglion) |
FFA

| Filter | Type | Function |
|---|---|---|
| Excitation filter | Cobalt blue (490 nm) | Incident white light filtered → blue light enters eye → excites fluorescein |
| Barrier filter | Yellow-green | Blocks reflected blue light; allows only emitted yellow-green (530 nm) fluorescence through |
| Phase | Timing | Description |
|---|---|---|
| Pre-arterial / Choroidal phase | 8-15 sec after injection | Choroidal background flush; patchy, irregular; choroid fills completely within 5 sec of first appearance |
| Arterial phase | 1-2 sec after choroidal filling | Retinal arteries fill; veins still empty |
| Arteriovenous (AV) / Laminar flow phase | ~18-20 sec | Laminar flow in veins - fluorescein flows along walls of veins while centre remains dark |
| Venous phase | Complete venous filling | AV transit time (arterial filling → complete venous filling) = normally <11 seconds |
| Recirculation phase | 45-60 sec after arterial phase | Dye recirculates; intensity fades |
| Late phase | 10-30 min post-injection | Dye cleared; only staining or leakage sites remain bright |
Arm-to-retina time (normal ~8-15 sec): time from IV injection to first choroidal appearance. Prolonged in carotid artery disease.
| Event | Frequency |
|---|---|
| Discoloration of skin and urine | Invariable (100%) |
| Nausea / vomiting | Nausea 10%, vomiting 2% |
| Extravasation at injection site | Local painful necrosis - treat with cold compress |
| Itching / rash / urticaria | Uncommon |
| Sneezing / wheezing | Uncommon |
| Vasovagal episode / syncope | Occasional (anxiety or cardiac cause) |
| Anaphylactic/anaphylactoid reaction | 1:2000 angiograms |
| Myocardial infarction | Extremely rare |
| Death | 1:220,000 (largest study) |
Note: OCT has largely replaced FFA for macular hole (Kanski: "In light of the effectiveness of OCT in macular hole, there is no need for FFA").
| Feature | FFA | ICGA |
|---|---|---|
| Dye | Sodium fluorescein | Indocyanine green |
| Excitation | 490 nm (blue) | 790 nm (near-infrared) |
| Emission | 530 nm (yellow-green) | 835 nm (infrared) |
| Protein binding | 70-80% | 98% |
| Penetrates RPE? | No (blocked by RPE) | Yes (near-infrared passes through RPE) |
| Best for | Retinal + inner BRB | Choroidal circulation, occult CNV, PCV |
| Contraindication | Severe fluorescein allergy | Iodine allergy, shellfish allergy, thyroid disease, pregnancy |
| Point | Answer |
|---|---|
| Excitation wavelength of fluorescein | 490 nm (blue) |
| Emission wavelength | 530 nm (yellow-green) |
| % protein bound | >70% |
| Urine discolouration duration | 24-36 hours |
| FFA dose | 5 ml of 10% solution |
| Normal arm-to-retina time | 8-15 seconds |
| Normal AV transit time | <11 seconds |
| FAZ diameter | 300-500 microns |
| Hyperfluorescence that increases in size and brightness | Leakage |
| Hyperfluorescence with fixed margins | Pooling |
| Hyperfluorescence in late phase with fixed borders | Staining |
| Hyperfluorescence that appears early and stays stable | Window defect |
| Hypofluorescence due to RPE hyperplasia / haemorrhage | Blockage |
| Hypofluorescence due to CRAO | Filling defect |
| Death rate in FFA | 1:220,000 |
| Anaphylaxis rate | 1:2,000 |
| Outer BRB formed by | RPE tight junctions (zonula occludentes) |
| Inner BRB formed by | Retinal capillary endothelial cell tight junctions |
| Pre-injection fluorescence = | Autofluorescence (optic disc drusen, lipofuscin) |
| FFA replaced by OCT for | Macular hole staging |
| Best investigation for PCV / occult CNV | ICGA (not FFA) |
Create a summary PDF on FFA phases and normal findings
ls -la /tmp/workspace/ 2>/dev/null || mkdir -p /tmp/workspace && echo "created"
mkdir -p /tmp/workspace/ffa-pdf && python3 << 'EOF' from reportlab.lib.pagesizes import A4 from reportlab.lib import colors from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle from reportlab.lib.units import cm, mm from reportlab.platypus import (SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, HRFlowable, KeepTogether) from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY from reportlab.platypus import PageBreak import urllib.request import io from reportlab.platypus import Image as RLImage # ── Output path ────────────────────────────────────────────────────────────── OUTPUT = "/tmp/workspace/ffa-pdf/FFA_Summary.pdf" # ── Download FFA image ─────────────────────────────────────────────────────── img_url = "https://cdn.orris.care/cdss_images/faa1c6eeeaebe3a9c49945c75f96e09710eaaf91d16e7c3e25976200713f31f4.png" try: req = urllib.request.Request(img_url, headers={"User-Agent": "Mozilla/5.0"}) img_data = urllib.request.urlopen(req, timeout=10).read() img_io = io.BytesIO(img_data) ffa_img = RLImage(img_io, width=7*cm, height=7*cm) has_img = True except Exception as e: print(f"Image download failed: {e}") has_img = False # ── Document setup ─────────────────────────────────────────────────────────── doc = SimpleDocTemplate( OUTPUT, pagesize=A4, leftMargin=1.8*cm, rightMargin=1.8*cm, topMargin=1.8*cm, bottomMargin=1.8*cm, title="FFA - Phases & Normal Findings", author="Kanski / Wills Eye Manual" ) W, H = A4 content_width = W - 3.6*cm # ── Colour palette ─────────────────────────────────────────────────────────── DARK_BLUE = colors.HexColor("#1A3A5C") MID_BLUE = colors.HexColor("#2E6DA4") LIGHT_BLUE = colors.HexColor("#D6E8F7") TEAL = colors.HexColor("#1A7A7A") TEAL_LIGHT = colors.HexColor("#D0EFEF") ORANGE = colors.HexColor("#E07B20") ORANGE_LIGHT= colors.HexColor("#FDE9D1") GREEN = colors.HexColor("#2E7D32") GREEN_LIGHT = colors.HexColor("#E8F5E9") RED = colors.HexColor("#C62828") RED_LIGHT = colors.HexColor("#FFEBEE") YELLOW_LIGHT= colors.HexColor("#FFFDE7") GREY_LIGHT = colors.HexColor("#F5F5F5") WHITE = colors.white BLACK = colors.HexColor("#212121") GREY_TEXT = colors.HexColor("#555555") # ── Styles ─────────────────────────────────────────────────────────────────── styles = getSampleStyleSheet() title_style = ParagraphStyle("Title", fontName="Helvetica-Bold", fontSize=22, textColor=WHITE, alignment=TA_CENTER, spaceAfter=4) subtitle_style = ParagraphStyle("Subtitle", fontName="Helvetica", fontSize=11, textColor=colors.HexColor("#CCE5FF"), alignment=TA_CENTER, spaceAfter=2) source_style = ParagraphStyle("Source", fontName="Helvetica-Oblique", fontSize=8.5, textColor=colors.HexColor("#AACCEE"), alignment=TA_CENTER) h1_style = ParagraphStyle("H1", fontName="Helvetica-Bold", fontSize=13, textColor=WHITE, alignment=TA_LEFT, spaceAfter=2, spaceBefore=4, leftIndent=4, leading=17) h2_style = ParagraphStyle("H2", fontName="Helvetica-Bold", fontSize=10.5, textColor=DARK_BLUE, spaceAfter=2, spaceBefore=6, leading=14) body_style = ParagraphStyle("Body", fontName="Helvetica", fontSize=9.5, textColor=BLACK, leading=14, spaceAfter=2, alignment=TA_JUSTIFY) small_style = ParagraphStyle("Small", fontName="Helvetica", fontSize=8.5, textColor=GREY_TEXT, leading=12, spaceAfter=1) bold_small = ParagraphStyle("BoldSmall", fontName="Helvetica-Bold", fontSize=9, textColor=DARK_BLUE, leading=13) tip_style = ParagraphStyle("Tip", fontName="Helvetica-Oblique", fontSize=9, textColor=TEAL, leading=13, leftIndent=8, spaceAfter=3) bullet_style = ParagraphStyle("Bullet", fontName="Helvetica", fontSize=9.5, textColor=BLACK, leading=14, leftIndent=12, bulletIndent=2, spaceAfter=1) cell_style = ParagraphStyle("Cell", fontName="Helvetica", fontSize=9, textColor=BLACK, leading=12) cell_bold = ParagraphStyle("CellBold", fontName="Helvetica-Bold", fontSize=9, textColor=DARK_BLUE, leading=12) cell_hdr = ParagraphStyle("CellHdr", fontName="Helvetica-Bold", fontSize=9.5, textColor=WHITE, leading=13, alignment=TA_CENTER) mcq_val = ParagraphStyle("MCQVal", fontName="Helvetica-Bold", fontSize=9, textColor=RED, leading=12) # ── Helper: section header row ─────────────────────────────────────────────── def section_header(text, bg=MID_BLUE): row = [[Paragraph(text, h1_style)]] t = Table(row, colWidths=[content_width]) t.setStyle(TableStyle([ ("BACKGROUND", (0,0), (-1,-1), bg), ("ROWPADDING", (0,0), (-1,-1), 6), ("TOPPADDING", (0,0), (-1,-1), 7), ("BOTTOMPADDING", (0,0), (-1,-1), 7), ("LEFTPADDING", (0,0), (-1,-1), 10), ("BOX", (0,0), (-1,-1), 0.5, colors.HexColor("#0D2744")), ("ROUNDEDCORNERS", [4,4,4,4]), ])) return t def hr(color=MID_BLUE, thickness=1): return HRFlowable(width="100%", thickness=thickness, color=color, spaceAfter=4, spaceBefore=4) # ── TITLE BLOCK ────────────────────────────────────────────────────────────── def title_block(): rows = [ [Paragraph("FUNDUS FLUORESCEIN ANGIOGRAPHY", title_style)], [Paragraph("Phases & Normal Findings — PG Ophthalmology Quick Reference", subtitle_style)], [Paragraph("Source: Kanski's Clinical Ophthalmology 10th Ed. (pp. 568–590) · Wills Eye Manual (pp. 1100–1102)", source_style)], ] t = Table(rows, colWidths=[content_width]) t.setStyle(TableStyle([ ("BACKGROUND", (0,0), (-1,-1), DARK_BLUE), ("TOPPADDING", (0,0), (-1,-1), 8), ("BOTTOMPADDING", (0,0), (-1,-1), 8), ("LEFTPADDING", (0,0), (-1,-1), 12), ("RIGHTPADDING", (0,0), (-1,-1), 12), ("BOX", (0,0), (-1,-1), 1, colors.HexColor("#0D2744")), ])) return t # ── PRINCIPLE TABLE ────────────────────────────────────────────────────────── def principle_table(): data = [ [Paragraph("PARAMETER", cell_hdr), Paragraph("DETAIL", cell_hdr)], [Paragraph("Dye", cell_bold), Paragraph("Sodium fluorescein — orange, water-soluble dye", cell_style)], [Paragraph("Excitation peak", cell_bold), Paragraph("<b>~490 nm</b> (blue light — absorbed by fluorescein)", cell_style)], [Paragraph("Emission peak", cell_bold), Paragraph("<b>~530 nm</b> (yellow-green fluorescence emitted)", cell_style)], [Paragraph("Protein binding", cell_bold), Paragraph("<b>>70%</b> bound to serum proteins; does NOT cross intact BRB", cell_style)], [Paragraph("Excretion", cell_bold), Paragraph("Urine over <b>24–36 hours</b> (bright yellow — warn patient!)", cell_style)], [Paragraph("Dose", cell_bold), Paragraph("<b>5 ml of 10% solution</b> (= 500 mg) IV over 5–10 seconds", cell_style)], [Paragraph("Excitation filter", cell_bold), Paragraph("Cobalt blue (~490 nm) — blue light enters eye, excites fluorescein", cell_style)], [Paragraph("Barrier filter", cell_bold), Paragraph("Yellow-green — blocks reflected blue; passes only emitted 530 nm light", cell_style)], ] col = [4.5*cm, content_width - 4.5*cm] t = Table(data, colWidths=col, repeatRows=1) t.setStyle(TableStyle([ ("BACKGROUND", (0,0), (-1,0), MID_BLUE), ("BACKGROUND", (0,1), (-1,1), LIGHT_BLUE), ("BACKGROUND", (0,2), (-1,2), WHITE), ("BACKGROUND", (0,3), (-1,3), LIGHT_BLUE), ("BACKGROUND", (0,4), (-1,4), WHITE), ("BACKGROUND", (0,5), (-1,5), LIGHT_BLUE), ("BACKGROUND", (0,6), (-1,6), WHITE), ("BACKGROUND", (0,7), (-1,7), LIGHT_BLUE), ("BACKGROUND", (0,8), (-1,8), WHITE), ("ROWPADDING", (0,0), (-1,-1), 5), ("TOPPADDING", (0,0), (-1,-1), 5), ("BOTTOMPADDING", (0,0), (-1,-1), 5), ("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#B0C4DE")), ("BOX", (0,0), (-1,-1), 0.8, MID_BLUE), ("VALIGN", (0,0), (-1,-1), "MIDDLE"), ])) return t # ── PHASES TABLE ───────────────────────────────────────────────────────────── def phases_table(): data = [ [Paragraph("PHASE", cell_hdr), Paragraph("TIMING", cell_hdr), Paragraph("WHAT IS SEEN", cell_hdr)], [Paragraph("1. Pre-arterial\n(Choroidal) Phase", cell_bold), Paragraph("<b>8–15 sec</b> post-injection\n(choroid fills in ~5 sec)", cell_style), Paragraph("Patchy background choroidal fluorescence (incomplete/lobular); ciliary arteries fill first; NO retinal vessels yet", cell_style)], [Paragraph("2. Arterial Phase", cell_bold), Paragraph("<b>1–2 sec</b> after choroidal filling", cell_style), Paragraph("Retinal arteries fill; veins still empty; arteries appear bright white", cell_style)], [Paragraph("3. Arteriovenous\n(Laminar) Phase", cell_bold), Paragraph("~18–20 sec post-injection", cell_style), Paragraph("Laminar flow in veins — dye flows along walls; centre of veins still dark; arteries fully bright", cell_style)], [Paragraph("4. Venous Phase", cell_bold), Paragraph("Complete venous filling\n<b>AV transit <11 sec</b>", cell_style), Paragraph("Veins completely filled; arteries start to fade; peak retinal fluorescence; disc autofluorescence prominent", cell_style)], [Paragraph("5. Recirculation\nPhase", cell_bold), Paragraph("<b>45–60 sec</b> after arterial phase", cell_style), Paragraph("Dye recirculates at lower concentration; overall intensity fading; abnormalities (leakage) become clearer", cell_style)], [Paragraph("6. Late Phase", cell_bold), Paragraph("<b>10–30 min</b> post-injection", cell_style), Paragraph("Dye cleared from vasculature; only staining and leakage sites remain bright; optic disc may stain mildly", cell_style)], ] col = [3.5*cm, 3.5*cm, content_width - 7*cm] t = Table(data, colWidths=col, repeatRows=1) bg_rows = [TEAL_LIGHT, WHITE, TEAL_LIGHT, WHITE, TEAL_LIGHT, WHITE] ts = [ ("BACKGROUND", (0,0), (-1,0), TEAL), ("ROWPADDING", (0,0), (-1,-1), 5), ("TOPPADDING", (0,0), (-1,-1), 6), ("BOTTOMPADDING", (0,0), (-1,-1), 6), ("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#9ECFCF")), ("BOX", (0,0), (-1,-1), 0.8, TEAL), ("VALIGN", (0,0), (-1,-1), "MIDDLE"), ] for i, bg in enumerate(bg_rows): ts.append(("BACKGROUND", (0, i+1), (-1, i+1), bg)) t.setStyle(TableStyle(ts)) return t # ── NORMAL FINDINGS TABLE ───────────────────────────────────────────────────── def normal_findings_table(): data = [ [Paragraph("FEATURE", cell_hdr), Paragraph("NORMAL APPEARANCE / VALUE", cell_hdr)], [Paragraph("Arm-to-retina time", cell_bold), Paragraph("<b>8–15 seconds</b> (time from IV injection to first choroidal flush)", cell_style)], [Paragraph("Retinal AV transit time", cell_bold), Paragraph("<b>< 11 seconds</b> (arterial → complete venous filling)", cell_style)], [Paragraph("Retinal arteries", cell_bold), Paragraph("Bright white, thin, well-defined borders during arterial phase", cell_style)], [Paragraph("Retinal veins", cell_bold), Paragraph("Laminar flow → complete fill; slightly wider than arteries; no leakage", cell_style)], [Paragraph("Optic disc", cell_bold), Paragraph("Mild late staining normal; disc capillaries fluoresce early; no leakage", cell_style)], [Paragraph("Foveal dark spot", cell_bold), Paragraph("Normal hypofluorescence due to xanthophyll pigment + tall RPE cells (melanin/lipofuscin)", cell_style)], [Paragraph("Foveal Avascular Zone (FAZ)", cell_bold), Paragraph("<b>300–500 microns</b> diameter; no retinal capillaries in this zone", cell_style)], [Paragraph("Choroidal background", cell_bold), Paragraph("Patchy/lobular early; evenly bright in venous phase; NOT visible as individual vessels on FFA", cell_style)], [Paragraph("RPE", cell_bold), Paragraph("Blocks choroidal fluorescence (tight junctions = outer BRB); uniform mild background masking", cell_style)], [Paragraph("Late phase", cell_bold), Paragraph("Vessels fade; only mild disc staining; no hyperfluorescent foci in normal eye", cell_style)], ] col = [4.8*cm, content_width - 4.8*cm] t = Table(data, colWidths=col, repeatRows=1) ts = [ ("BACKGROUND", (0,0), (-1,0), ORANGE), ("ROWPADDING", (0,0), (-1,-1), 5), ("TOPPADDING", (0,0), (-1,-1), 5), ("BOTTOMPADDING", (0,0), (-1,-1), 5), ("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#F5C896")), ("BOX", (0,0), (-1,-1), 0.8, ORANGE), ("VALIGN", (0,0), (-1,-1), "MIDDLE"), ] for i in range(1, len(data)): bg = ORANGE_LIGHT if i % 2 == 1 else WHITE ts.append(("BACKGROUND", (0, i), (-1, i), bg)) t.setStyle(TableStyle(ts)) return t # ── BRB TABLE ───────────────────────────────────────────────────────────────── def brb_table(): data = [ [Paragraph("BARRIER", cell_hdr), Paragraph("STRUCTURE", cell_hdr), Paragraph("MECHANISM", cell_hdr)], [Paragraph("Outer BRB", cell_bold), Paragraph("RPE (Retinal Pigment Epithelium)", cell_style), Paragraph("Tight junctions (zonula occludentes) between RPE cells; blocks fluorescein from entering subretinal space. Choriocapillaris is FENESTRATED — freely permeable, crosses Bruch membrane freely.", cell_style)], [Paragraph("Inner BRB", cell_bold), Paragraph("Retinal capillary endothelial cells", cell_style), Paragraph("Tight junctions between endothelial cells; neither bound nor free fluorescein crosses in health. BM and pericytes play minor role.", cell_style)], ] col = [2.8*cm, 4.0*cm, content_width - 6.8*cm] t = Table(data, colWidths=col, repeatRows=1) t.setStyle(TableStyle([ ("BACKGROUND", (0,0), (-1,0), MID_BLUE), ("BACKGROUND", (0,1), (-1,1), LIGHT_BLUE), ("BACKGROUND", (0,2), (-1,2), WHITE), ("ROWPADDING", (0,0), (-1,-1), 5), ("TOPPADDING", (0,0), (-1,-1), 6), ("BOTTOMPADDING", (0,0), (-1,-1), 6), ("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#B0C4DE")), ("BOX", (0,0), (-1,-1), 0.8, MID_BLUE), ("VALIGN", (0,0), (-1,-1), "MIDDLE"), ])) return t # ── MCQ BOX ─────────────────────────────────────────────────────────────────── def mcq_table(): data = [ [Paragraph("MCQ POINT", cell_hdr), Paragraph("ANSWER", cell_hdr)], [Paragraph("Excitation wavelength of fluorescein", cell_style), Paragraph("490 nm (blue)", mcq_val)], [Paragraph("Emission wavelength", cell_style), Paragraph("530 nm (yellow-green)", mcq_val)], [Paragraph("% protein binding", cell_style), Paragraph(">70%", mcq_val)], [Paragraph("Normal arm-to-retina time", cell_style), Paragraph("8–15 seconds", mcq_val)], [Paragraph("Normal AV transit time", cell_style), Paragraph("< 11 seconds", mcq_val)], [Paragraph("FAZ diameter", cell_style), Paragraph("300–500 microns", mcq_val)], [Paragraph("Urine discolouration duration", cell_style), Paragraph("24–36 hours", mcq_val)], [Paragraph("FFA dose", cell_style), Paragraph("5 ml of 10% solution IV", mcq_val)], [Paragraph("Foveal dark spot cause", cell_style), Paragraph("Xanthophyll pigment + tall RPE cells (melanin/lipofuscin)", mcq_val)], [Paragraph("Outer BRB formed by", cell_style), Paragraph("RPE tight junctions (zonula occludentes)", mcq_val)], [Paragraph("Inner BRB formed by", cell_style), Paragraph("Retinal capillary endothelial cell tight junctions", mcq_val)], [Paragraph("First phase seen on FFA", cell_style), Paragraph("Choroidal (pre-arterial) phase", mcq_val)], [Paragraph("Laminar flow seen in which phase?", cell_style), Paragraph("Arteriovenous (AV) phase", mcq_val)], [Paragraph("Death rate in FFA", cell_style), Paragraph("1:220,000", mcq_val)], [Paragraph("Anaphylaxis rate", cell_style), Paragraph("1:2,000", mcq_val)], ] col = [content_width * 0.55, content_width * 0.45] ts = [ ("BACKGROUND", (0,0), (-1,0), RED), ("ROWPADDING", (0,0), (-1,-1), 4), ("TOPPADDING", (0,0), (-1,-1), 5), ("BOTTOMPADDING", (0,0), (-1,-1), 5), ("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#EF9A9A")), ("BOX", (0,0), (-1,-1), 0.8, RED), ("VALIGN", (0,0), (-1,-1), "MIDDLE"), ] for i in range(1, len(data)): bg = RED_LIGHT if i % 2 == 1 else WHITE ts.append(("BACKGROUND", (0, i), (-1, i), bg)) t = Table(data, colWidths=col, repeatRows=1) t.setStyle(TableStyle(ts)) return t # ── ADVERSE EFFECTS ─────────────────────────────────────────────────────────── def adverse_table(): data = [ [Paragraph("ADVERSE EVENT", cell_hdr), Paragraph("FREQUENCY / NOTES", cell_hdr)], [Paragraph("Skin & urine discolouration", cell_style), Paragraph("Invariable (100%) — warn every patient", cell_style)], [Paragraph("Nausea", cell_style), Paragraph("~10%", cell_style)], [Paragraph("Vomiting", cell_style), Paragraph("~2%", cell_style)], [Paragraph("Extravasation at injection site", cell_style), Paragraph("Painful local necrosis — treat with cold compress", cell_style)], [Paragraph("Anaphylaxis / anaphylactoid", cell_style), Paragraph("<b>1:2,000</b> angiograms", cell_bold)], [Paragraph("Death", cell_style), Paragraph("<b>1:220,000</b> (largest study)", cell_bold)], [Paragraph("Allergy to iodine / seafood", cell_style), Paragraph("NOT a contraindication (fluorescein ≠ iodinated contrast)", cell_style)], ] col = [content_width * 0.45, content_width * 0.55] ts = [ ("BACKGROUND", (0,0), (-1,0), GREEN), ("ROWPADDING", (0,0), (-1,-1), 4), ("TOPPADDING", (0,0), (-1,-1), 5), ("BOTTOMPADDING", (0,0), (-1,-1), 5), ("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#A5D6A7")), ("BOX", (0,0), (-1,-1), 0.8, GREEN), ("VALIGN", (0,0), (-1,-1), "MIDDLE"), ] for i in range(1, len(data)): bg = GREEN_LIGHT if i % 2 == 1 else WHITE ts.append(("BACKGROUND", (0, i), (-1, i), bg)) t = Table(data, colWidths=col, repeatRows=1) t.setStyle(TableStyle(ts)) return t # ── IMAGE + CAPTION ─────────────────────────────────────────────────────────── def image_block(): caption = Paragraph( "<i>Fig: Normal FFA (Wills Eye Manual) — Note bright optic disc, retinal vasculature, " "and the foveal dark spot (foveal hypofluorescence) at centre-left.</i>", ParagraphStyle("ImgCaption", fontName="Helvetica-Oblique", fontSize=8.5, textColor=GREY_TEXT, alignment=TA_CENTER, leading=12)) if has_img: img_row = [[ffa_img, caption]] t = Table(img_row, colWidths=[7.5*cm, content_width - 8*cm]) t.setStyle(TableStyle([ ("VALIGN", (0,0), (-1,-1), "MIDDLE"), ("LEFTPADDING", (0,0), (-1,-1), 4), ("RIGHTPADDING", (0,0), (-1,-1), 4), ("BOX", (0,0), (-1,-1), 0.5, colors.HexColor("#B0C4DE")), ("BACKGROUND", (0,0), (-1,-1), GREY_LIGHT), ])) return t else: return Paragraph("[Normal FFA image unavailable]", small_style) # ── BUILD STORY ─────────────────────────────────────────────────────────────── story = [] # Title story.append(title_block()) story.append(Spacer(1, 6*mm)) # Principle & Physics story.append(section_header("1. PRINCIPLE & PHYSICS")) story.append(Spacer(1, 3*mm)) story.append(principle_table()) story.append(Spacer(1, 5*mm)) # Blood-Retinal Barriers story.append(section_header("2. BLOOD-RETINAL BARRIERS (BRB)", bg=DARK_BLUE)) story.append(Spacer(1, 3*mm)) story.append(brb_table()) story.append(Spacer(1, 5*mm)) # Phases story.append(section_header("3. ANGIOGRAPHIC PHASES", bg=TEAL)) story.append(Spacer(1, 3*mm)) story.append(Paragraph( "Fluorescein enters via the ophthalmic artery → short posterior ciliary arteries " "(choroid) → central retinal artery (retina). Images taken at <b>1–2 sec intervals</b> initially.", body_style)) story.append(Spacer(1, 2*mm)) story.append(phases_table()) story.append(Spacer(1, 3*mm)) story.append(Paragraph( "<b>Tip:</b> AV transit time <11 sec is normal. Prolonged arm-to-retina time (>15 sec) " "suggests carotid artery disease or severe cardiac failure.", tip_style)) story.append(Spacer(1, 5*mm)) # Normal findings + image side by side story.append(section_header("4. NORMAL FINDINGS", bg=ORANGE)) story.append(Spacer(1, 3*mm)) story.append(image_block()) story.append(Spacer(1, 3*mm)) story.append(normal_findings_table()) story.append(Spacer(1, 5*mm)) # Adverse effects story.append(section_header("5. ADVERSE EFFECTS & CONTRAINDICATIONS", bg=GREEN)) story.append(Spacer(1, 3*mm)) story.append(adverse_table()) story.append(Spacer(1, 3*mm)) story.append(Paragraph( "<b>Contraindications:</b> Severe fluorescein allergy (absolute). Relative: pregnancy, " "renal failure (lower dose), moderate-severe asthma, significant cardiac disease. " "<b>Iodine/seafood allergy is NOT a contraindication.</b>", body_style)) story.append(Spacer(1, 5*mm)) # MCQ story.append(section_header("6. HIGH-YIELD PG MCQ POINTS", bg=RED)) story.append(Spacer(1, 3*mm)) story.append(mcq_table()) story.append(Spacer(1, 5*mm)) # Footer note footer = Paragraph( "Reference: Kanski's Clinical Ophthalmology 10th Ed. pp. 568–590 • Wills Eye Manual pp. 1100–1102", ParagraphStyle("Footer", fontName="Helvetica-Oblique", fontSize=8, textColor=GREY_TEXT, alignment=TA_CENTER)) story.append(hr(color=GREY_TEXT, thickness=0.5)) story.append(footer) # ── Build PDF ───────────────────────────────────────────────────────────────── doc.build(story) print("PDF created successfully:", OUTPUT) EOF
ls -lh /tmp/workspace/ffa-pdf/
FFA Summary
PDF Document · PDF
| Section | Content |
|---|---|
| 1. Principle & Physics | Dye properties, excitation/emission wavelengths, protein binding, filters |
| 2. Blood-Retinal Barriers | Outer BRB (RPE tight junctions) vs Inner BRB (endothelial tight junctions), choriocapillaris fenestrations |
| 3. Angiographic Phases | All 6 phases with exact timings and what is seen — colour-coded teal table |
| 4. Normal Findings | Normal FFA photograph + table covering arm-to-retina time, AV transit, FAZ, foveal dark spot, disc, choroid, RPE |
| 5. Adverse Effects | Full table with frequencies (nausea 10%, anaphylaxis 1:2000, death 1:220,000) + contraindications |
| 6. PG MCQ Points | 15 high-yield Q&A pairs (wavelengths, timings, FAZ size, BRB anatomy, phases) |
Create diagram
Which diagram would you like for FFA?
Macular hole
| Stage | Name | Features |
|---|---|---|
| 1a | Impending hole / VMT | Yellow spot at fovea; flattening of foveal depression; no true hole |
| 1b | Occult hole / VMA | Yellow ring; foveal detachment; inner layers separate from photoreceptors |
| 2 | Early FTMH | Small full-thickness hole <400 μm; central, eccentric, or crescent-shaped; posterior hyaloid still attached |
| 3 | Full-thickness hole | FTMH ≥400 μm; cuff of subretinal fluid (SRF); no PVD (hyaloid attached) |
| 4 | Full-thickness hole | FTMH with cuff of SRF; complete PVD present (Weiss ring visible) |
| Category | Size | VMT Status |
|---|---|---|
| VMA | - | Vitreous attached to fovea, no distortion of foveal contour |
| VMT | - | Foveal contour distorted, no full-thickness hole |
| Small FTMH + VMT | <250 μm | - |
| Medium FTMH + VMT | 250-400 μm | - |
| Large FTMH | >400 μm | With or without VMT |
Note: Size measured at the narrowest point on OCT.

TIP (Kanski): OCT is key to confirmation of diagnosis and staging. FFA is NOT needed.
| Condition | Key Distinguishing Feature |
|---|---|
| Pseudohole (ERM) | No loss of foveal tissue; ERM/ILM sheen visible; Watzke-Allen negative |
| Lamellar hole | Partial thickness only; not as red; no surrounding grey halo; OCT shows intact outer retina |
| CME (cystoid) | Intraretinal cysts; no full-thickness defect; underlying cause (uveitis, post-op) |
| Solar retinopathy | Small yellow/red lesion + fine grey pigment; history of sun gazing |
| Adult vitelliform dystrophy | Bilateral; yellowish subfoveal material; ERG/EOG abnormal |
| CSCR | Subretinal fluid; younger males; OCT shows neurosensory detachment |
| Subfoveal drusen | Scattered yellow deposits; no full-thickness defect |
| Outcome | Result |
|---|---|
| Hole closure rate | Most cases |
| Visual improvement (over months) | 80-90% of eyes |
| Final VA ≥ 6/12 | ~65% |
| Visual worsening | Up to 10% |
Best results: Smaller holes present for <6 months before surgery.
| Term | Definition |
|---|---|
| VMA (Vitreomacular Adhesion) | Vitreous attached within 3 mm of central macula; no foveal distortion; usually dynamic, may not progress |
| VMT (Vitreomacular Traction) | VMA + foveal distortion/structural changes on OCT |
| Focal VMA/VMT | ≤1500 μm diameter |
| Broad VMA/VMT | >1500 μm |
| Point | Answer |
|---|---|
| Most common age/sex | Females, 60-70 years |
| Bilateral presentation | ~30% |
| Risk to fellow eye (5 years) | ~10% (less if PVD present) |
| Primary mechanism | Vitreomacular traction (VMT) |
| Stage 1 spontaneous resolution | ~50% |
| Investigation of choice | OCT |
| Watzke-Allen test in macular hole | Beam thinned or broken |
| FFA finding | Window defect (early, well-defined) |
| FAF in stage 3/4 | Hyperfluorescent foveolar spot |
| Surgery indication | Stage 2 or greater |
| Surgery steps | Vitrectomy + ILM peeling + gas tamponade |
| ILM staining dye | Brilliant blue / ICG |
| Post-op positioning | Face-down 8 hrs/day for 5 days |
| Success rate (hole closure + VA improvement) | 80-90% visual improvement; 65% achieve ≥6/12 |
| Best surgical outcome predictor | Smaller hole, <6 months duration |
| Ocriplasmin mechanism | Dissolves fibronectin + laminin at VMT site |
| Size classification (IVTS) | Small <250 μm, Medium 250-400 μm, Large >400 μm |
| Old Gass Stage 3 vs 4 | Stage 3 = no PVD; Stage 4 = complete PVD (Weiss ring) |
| Pseudohole vs true hole distinction | OCT (no tissue loss in pseudohole); Watzke-Allen |
| Partial thickness = | Lamellar hole (not full-thickness macular hole) |
Create a visual macular hole staging diagram (Gass vs IVTS classification)
mkdir -p /tmp/workspace/ffa-pdf && python3 << 'PYEOF' import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt import matplotlib.patches as mpatches from matplotlib.patches import FancyArrowPatch, FancyBboxPatch, Arc, Wedge import matplotlib.patheffects as pe import numpy as np # ── Canvas ──────────────────────────────────────────────────────────────────── fig = plt.figure(figsize=(20, 26), facecolor='#0D1B2A') fig.subplots_adjust(left=0, right=1, top=1, bottom=0) ax = fig.add_axes([0, 0, 1, 1]) ax.set_xlim(0, 20) ax.set_ylim(0, 26) ax.axis('off') # ── Palette ─────────────────────────────────────────────────────────────────── C = { 'bg': '#0D1B2A', 'panel': '#132338', 'accent1': '#1A7CC2', # blue 'accent2': '#17A589', # teal 'accent3': '#E67E22', # orange 'accent4': '#8E44AD', # purple 'gass_hdr': '#1A5276', 'ivts_hdr': '#1A5E20', 'gass_bg': '#1B3A5C', 'ivts_bg': '#1B4820', 'text': '#ECF0F1', 'subtext': '#AEB6BF', 'yellow': '#F1C40F', 'red': '#E74C3C', 'green': '#27AE60', 'white': '#FFFFFF', 'retina_n': '#C0A882', # normal retina 'retina_h': '#8B6E55', # retina darker 'vitreous': '#1A4570', # vitreous 'vit_face': '#2E6DA4', # posterior hyaloid 'srf': '#5DADE2', # subretinal fluid 'vmtline': '#E74C3C', # traction line 'rpe': '#6E3B1A', # RPE } # ══════════════════════════════════════════════════════════════════════════════ # TITLE # ══════════════════════════════════════════════════════════════════════════════ title_box = FancyBboxPatch((0.2, 24.6), 19.6, 1.2, boxstyle="round,pad=0.1", facecolor='#1A2F47', edgecolor=C['accent1'], lw=2) ax.add_patch(title_box) ax.text(10, 25.35, 'MACULAR HOLE STAGING DIAGRAM', ha='center', va='center', fontsize=22, fontweight='bold', color=C['white'], fontfamily='DejaVu Sans') ax.text(10, 24.9, 'Gass Classification (Clinical) vs IVTS Classification (OCT-based)', ha='center', va='center', fontsize=13, color=C['accent2']) # ══════════════════════════════════════════════════════════════════════════════ # CROSS-SECTION DRAWING HELPER # ══════════════════════════════════════════════════════════════════════════════ def draw_retina_cross_section(ax, cx, cy, w=2.6, h=0.9, stage=None): """ Draw a stylised retinal cross-section for a given macular hole stage. cx, cy = centre of diagram stage: 'normal','1a','1b','2','3','4','vma','vmt','small','medium','large' """ rx = cx - w/2 # --- RPE layer (bottom dark brown bar) --- rpe = mpatches.Rectangle((rx, cy - h/2 - 0.15), w, 0.15, facecolor=C['rpe'], edgecolor='#3D1A08', lw=0.5) ax.add_patch(rpe) # --- Retina (orange-tan layer) --- # Build retina as two halves with foveal dip (or hole) retina_pts_x = np.linspace(rx, rx+w, 60) if stage == 'normal': # Normal foveal pit dip = 0.22 * np.exp(-((retina_pts_x - cx)**2) / (0.12)) retina_top = cy - h/2 + dip + 0.38 ax.fill_between(retina_pts_x, cy - h/2, retina_top, color=C['retina_n'], alpha=0.9, zorder=2) ax.plot(retina_pts_x, retina_top, color='#8B6E55', lw=1) # vitreous ax.fill_between(retina_pts_x, retina_top, cy + h/2 + 0.5, color=C['vitreous'], alpha=0.35, zorder=1) # Posterior hyaloid (attached everywhere) ax.plot([rx, rx+w], [cy + h/2 + 0.4, cy + h/2 + 0.4], color=C['vit_face'], lw=2.5, linestyle='-', zorder=3) elif stage == '1a': # VMT - vitreous still attached at fovea, pulling up, cyst forming dip = 0.1 * np.exp(-((retina_pts_x - cx)**2) / (0.12)) retina_top = cy - h/2 + 0.38 + dip # cyst: slight upward bulge at centre cyst = 0.12 * np.exp(-((retina_pts_x - cx)**2) / (0.04)) retina_top += cyst ax.fill_between(retina_pts_x, cy - h/2, retina_top, color=C['retina_n'], alpha=0.9, zorder=2) ax.plot(retina_pts_x, retina_top, color='#8B6E55', lw=1) # Yellow spot indicator ax.plot(cx, cy - h/2 + 0.4, 'o', color=C['yellow'], ms=8, zorder=5) # Vitreous ax.fill_between(retina_pts_x, retina_top, cy + h/2 + 0.5, color=C['vitreous'], alpha=0.35, zorder=1) # Posterior hyaloid with VMT attachment (V-shape pull) hx = [rx, cx - 0.3, cx, cx + 0.3, rx+w] hy_top = cy + h/2 + 0.4 fovea_y = retina_top[30] + 0.05 hy = [hy_top, hy_top - 0.15, fovea_y, hy_top - 0.15, hy_top] ax.plot(hx, hy, color=C['vmtline'], lw=2.5, zorder=4) # traction arrow ax.annotate('', xy=(cx, fovea_y + 0.25), xytext=(cx, fovea_y - 0.01), arrowprops=dict(arrowstyle='<-', color=C['vmtline'], lw=1.5)) elif stage == '1b': # Foveal detachment - small cystic space, yellowish ring dip = 0.05 * np.exp(-((retina_pts_x - cx)**2) / (0.15)) retina_top = cy - h/2 + 0.38 + dip # schisis/cyst cavity in outer retina schisis_depth = 0.18 * np.exp(-((retina_pts_x - cx)**2) / (0.07)) inner_split = cy - h/2 + 0.2 + schisis_depth ax.fill_between(retina_pts_x, cy - h/2, inner_split, color=C['retina_h'], alpha=0.9, zorder=2) ax.fill_between(retina_pts_x, inner_split, retina_top, color='#3D85C0', alpha=0.5, zorder=3) # cyst fluid ax.fill_between(retina_pts_x, retina_top, retina_top + 0.08, color=C['retina_n'], alpha=0.9, zorder=4) ax.plot(retina_pts_x, retina_top + 0.08, color='#8B6E55', lw=1) # Yellow ring ring = plt.Circle((cx, cy - h/2 + 0.38), 0.22, fill=False, edgecolor=C['yellow'], lw=2.5, zorder=6) ax.add_patch(ring) # Vitreous + VMT ax.fill_between(retina_pts_x, retina_top + 0.08, cy + h/2 + 0.5, color=C['vitreous'], alpha=0.35, zorder=1) hx = [rx, cx - 0.35, cx, cx + 0.35, rx+w] hy_top = cy + h/2 + 0.4 fovea_y = retina_top[30] + 0.1 hy = [hy_top, hy_top - 0.12, fovea_y, hy_top - 0.12, hy_top] ax.plot(hx, hy, color=C['vmtline'], lw=2.5, zorder=5) elif stage in ('2', 'small', 'medium'): hole_w = 0.18 if stage == '2' else (0.22 if stage == 'small' else 0.32) # Retina with hole left_x = retina_pts_x[retina_pts_x < (cx - hole_w/2)] right_x = retina_pts_x[retina_pts_x > (cx + hole_w/2)] for seg_x in [left_x, right_x]: seg_top = cy - h/2 + 0.38 + 0.05 * np.exp(-((seg_x - cx)**2)/0.15) ax.fill_between(seg_x, cy - h/2, seg_top, color=C['retina_n'], alpha=0.9, zorder=2) ax.plot(seg_x, seg_top, color='#8B6E55', lw=1) # Hole gap (dark) ax.fill_between([cx - hole_w/2, cx + hole_w/2], cy - h/2, cy - h/2 + 0.38, color='#0D1B2A', zorder=3) # SRF under hole (small amount) srf = mpatches.Ellipse((cx, cy - h/2 - 0.05), hole_w * 1.6, 0.12, facecolor=C['srf'], alpha=0.6, zorder=2) ax.add_patch(srf) # Operculum (flap above hole) op = mpatches.Ellipse((cx, cy + h/2 + 0.1), hole_w * 1.0, 0.1, facecolor=C['retina_n'], alpha=0.8, edgecolor='#8B6E55', lw=0.8, zorder=4) ax.add_patch(op) # Vitreous ax.fill_between(retina_pts_x, cy - h/2 + 0.38, cy + h/2 + 0.5, color=C['vitreous'], alpha=0.25, zorder=1) # Posterior hyaloid still attached (VMT) for stage 2/small/medium hx = [rx, cx - 0.4, cx - hole_w/2 - 0.02, cx + hole_w/2 + 0.02, cx + 0.4, rx+w] hy_top = cy + h/2 + 0.4 hy = [hy_top, hy_top - 0.05, cy - h/2 + 0.38, cy - h/2 + 0.38, hy_top - 0.05, hy_top] ax.plot(hx, hy, color=C['vmtline'], lw=2.5, zorder=5) elif stage in ('3', '4', 'large'): hole_w = 0.45 left_x = retina_pts_x[retina_pts_x < (cx - hole_w/2)] right_x = retina_pts_x[retina_pts_x > (cx + hole_w/2)] for seg_x in [left_x, right_x]: seg_top = cy - h/2 + 0.38 + 0.04 * np.exp(-((seg_x - cx)**2)/0.2) ax.fill_between(seg_x, cy - h/2, seg_top, color=C['retina_n'], alpha=0.9, zorder=2) ax.plot(seg_x, seg_top, color='#8B6E55', lw=1) ax.fill_between([cx - hole_w/2, cx + hole_w/2], cy - h/2, cy - h/2 + 0.38, color='#0D1B2A', zorder=3) # Large SRF cuff srf = mpatches.Ellipse((cx, cy - h/2 - 0.1), hole_w * 2.0, 0.2, facecolor=C['srf'], alpha=0.55, zorder=2) ax.add_patch(srf) # Operculum (free-floating) op_y = cy + h/2 + (0.3 if stage in ('3','large') else 0.55) op = mpatches.Ellipse((cx, op_y), hole_w * 0.9, 0.12, facecolor=C['retina_n'], alpha=0.8, edgecolor='#8B6E55', lw=0.8, zorder=5) ax.add_patch(op) # Vitreous ax.fill_between(retina_pts_x, cy - h/2 + 0.38, cy + h/2 + 0.65, color=C['vitreous'], alpha=0.25, zorder=1) # Posterior hyaloid: if stage == '3': # still partially attached (no PVD) ax.plot([rx, rx+w], [cy + h/2 + 0.2, cy + h/2 + 0.2], color=C['vit_face'], lw=2.5, linestyle='--', zorder=4) else: # complete PVD - hyaloid detached high up ax.plot([rx, rx+w], [cy + h/2 + 0.5, cy + h/2 + 0.5], color=C['vit_face'], lw=2.5, linestyle='-', zorder=4) # Weiss ring indicator ax.text(cx, cy + h/2 + 0.65, 'Weiss ring', ha='center', va='bottom', fontsize=7, color=C['vit_face'], style='italic') elif stage == 'vma': # Same as normal but with small attachment dot dip = 0.22 * np.exp(-((retina_pts_x - cx)**2) / (0.12)) retina_top = cy - h/2 + dip + 0.38 ax.fill_between(retina_pts_x, cy - h/2, retina_top, color=C['retina_n'], alpha=0.9, zorder=2) ax.plot(retina_pts_x, retina_top, color='#8B6E55', lw=1) ax.fill_between(retina_pts_x, retina_top, cy + h/2 + 0.5, color=C['vitreous'], alpha=0.35, zorder=1) # Posterior hyaloid with small attachment at fovea ax.plot([rx, cx - 0.5], [cy + h/2 + 0.4, cy + h/2 + 0.4], color=C['vit_face'], lw=2.5, zorder=3) ax.plot([cx + 0.5, rx+w], [cy + h/2 + 0.4, cy + h/2 + 0.4], color=C['vit_face'], lw=2.5, zorder=3) # attached segment (green dot) fovea_top = retina_top[30] ax.plot([cx - 0.45, cx + 0.45], [cy + h/2 + 0.4, cy + h/2 + 0.4], color=C['green'], lw=3, zorder=4) ax.annotate('', xy=(cx, fovea_top + 0.02), xytext=(cx, cy + h/2 + 0.39), arrowprops=dict(arrowstyle='-', color=C['green'], lw=1.5)) # ══════════════════════════════════════════════════════════════════════════════ # SECTION HEADERS # ══════════════════════════════════════════════════════════════════════════════ # Left: Gass | Right: IVTS # Divider line ax.plot([10, 10], [1.0, 24.4], color=C['accent1'], lw=1.5, alpha=0.5, linestyle='--') # Gass header gass_hdr = FancyBboxPatch((0.2, 22.8), 9.4, 1.4, boxstyle="round,pad=0.15", facecolor=C['gass_hdr'], edgecolor=C['accent1'], lw=1.5) ax.add_patch(gass_hdr) ax.text(4.9, 23.65, 'GASS CLASSIFICATION', ha='center', va='center', fontsize=15, fontweight='bold', color=C['white']) ax.text(4.9, 23.15, 'Clinical (Ophthalmoscopy-based)', ha='center', va='center', fontsize=10, color=C['accent2']) # IVTS header ivts_hdr = FancyBboxPatch((10.4, 22.8), 9.4, 1.4, boxstyle="round,pad=0.15", facecolor=C['ivts_hdr'], edgecolor=C['accent2'], lw=1.5) ax.add_patch(ivts_hdr) ax.text(15.1, 23.65, 'IVTS CLASSIFICATION', ha='center', va='center', fontsize=15, fontweight='bold', color=C['white']) ax.text(15.1, 23.15, 'OCT-based (International VMT Study Group)', ha='center', va='center', fontsize=10, color=C['accent2']) # ══════════════════════════════════════════════════════════════════════════════ # LEGEND (bottom) # ══════════════════════════════════════════════════════════════════════════════ legend_y = 1.35 legend_items = [ (C['retina_n'], 'Retina (neurosensory)'), (C['rpe'], 'RPE'), (C['srf'], 'Subretinal fluid (SRF)'), (C['vit_face'], 'Posterior hyaloid / vitreous face'), (C['vmtline'], 'VMT traction / attached hyaloid'), (C['yellow'], 'Yellow spot/ring'), (C['vitreous'], 'Vitreous'), ] leg_x = 0.5 for color, label in legend_items: rect = mpatches.Rectangle((leg_x, legend_y - 0.15), 0.5, 0.3, facecolor=color, alpha=0.9, edgecolor='none') ax.add_patch(rect) ax.text(leg_x + 0.65, legend_y, label, va='center', fontsize=8.5, color=C['subtext']) leg_x += 2.78 # ══════════════════════════════════════════════════════════════════════════════ # GASS STAGES (left side, 5 rows) # ══════════════════════════════════════════════════════════════════════════════ gass_stages = [ ('Normal', 'normal', None, ['Normal foveal pit', 'Vitreous attached', 'No pathology']), ('Stage 1a\n"Impending"', '1a', '#F1C40F', ['Yellow spot at fovea', 'VMT present', 'Foveal flattening', 'Inner–outer retina split']), ('Stage 1b\n"Occult"', '1b', '#E67E22', ['Yellow ring at fovea', 'Foveal detachment', 'Cystic schisis cavity', 'VMT present']), ('Stage 2\n"Early FTMH"', '2', '#E74C3C', ['FTMH < 400 µm', 'Hyaloid still attached', 'Central/eccentric', 'Operculum visible']), ('Stage 3\n"FTMH, no PVD"', '3', '#8E44AD', ['FTMH ≥ 400 µm', 'Cuff of SRF', 'NO PVD (hyaloid attached)', 'Free operculum']), ('Stage 4\n"FTMH + PVD"', '4', '#2E86C1', ['FTMH ≥ 400 µm', 'Cuff of SRF', 'Complete PVD present', 'Weiss ring visible']), ] row_h = 3.55 start_y = 22.35 for i, (label, stage_key, color, features) in enumerate(gass_stages): cy_row = start_y - i * row_h - row_h/2 + 0.1 # Panel background panel_col = C['gass_bg'] if i % 2 == 0 else '#16304D' panel = FancyBboxPatch((0.2, cy_row - 1.55), 9.4, row_h - 0.1, boxstyle="round,pad=0.08", facecolor=panel_col, edgecolor='#2E5A8A', lw=0.8, alpha=0.7) ax.add_patch(panel) # Stage label lbl_col = color if color else C['green'] ax.text(1.3, cy_row + 1.3, label, va='top', ha='center', fontsize=11, fontweight='bold', color=lbl_col, multialignment='center') # Cross-section diagram draw_retina_cross_section(ax, 3.8, cy_row + 0.1, w=2.8, h=0.85, stage=stage_key) # Feature bullets bx, by = 5.8, cy_row + 1.2 for feat in features: ax.text(bx, by, f'• {feat}', va='top', fontsize=9.2, color=C['text']) by -= 0.48 # Separator line if i < len(gass_stages) - 1: ax.plot([0.4, 9.4], [cy_row - 1.55, cy_row - 1.55], color='#2E5A8A', lw=0.6, alpha=0.5) # ══════════════════════════════════════════════════════════════════════════════ # IVTS CLASSIFICATION (right side) # ══════════════════════════════════════════════════════════════════════════════ ivts_stages = [ ('VMA\nVitreomacular Adhesion', 'vma', C['green'], ['Vitreous attached at fovea ≤3 mm', 'NO foveal distortion', 'Focal: ≤1500 µm', 'Broad: >1500 µm', 'Usually dynamic/resolves']), ('VMT\nVitreomacular Traction', '1a', C['accent3'], ['VMA + foveal distortion on OCT', 'No full-thickness hole', 'Focal: ≤1500 µm', 'Broad: >1500 µm', '≡ Gass Stage 1a/1b']), ('Small FTMH + VMT\n(<250 µm)', 'small', '#E74C3C', ['Full-thickness hole < 250 µm', 'Measured at narrowest point', 'VMT present', '≡ Gass Stage 2 (small)']), ('Medium FTMH + VMT\n(250–400 µm)', 'medium', '#C0392B', ['Full-thickness hole 250–400 µm', 'Measured at narrowest point', 'VMT present', '≡ Gass Stage 2 (medium)']), ('Large FTMH\n(>400 µm)', 'large', '#8E44AD', ['Full-thickness hole > 400 µm', 'With or without VMT', '≡ Gass Stage 3 / Stage 4', 'Poorest prognosis']), ] row_h2 = 4.24 start_y2 = 22.35 for i, (label, stage_key, color, features) in enumerate(ivts_stages): cy_row = start_y2 - i * row_h2 - row_h2/2 + 0.1 panel_col = C['ivts_bg'] if i % 2 == 0 else '#163A1F' panel = FancyBboxPatch((10.4, cy_row - 1.85), 9.4, row_h2 - 0.1, boxstyle="round,pad=0.08", facecolor=panel_col, edgecolor='#1E8449', lw=0.8, alpha=0.7) ax.add_patch(panel) ax.text(11.6, cy_row + 1.6, label, va='top', ha='center', fontsize=10.5, fontweight='bold', color=color, multialignment='center') draw_retina_cross_section(ax, 14.1, cy_row + 0.1, w=2.8, h=0.85, stage=stage_key) bx, by = 15.9, cy_row + 1.55 for feat in features: ax.text(bx, by, f'• {feat}', va='top', fontsize=9.2, color=C['text']) by -= 0.46 if i < len(ivts_stages) - 1: ax.plot([10.6, 19.6], [cy_row - 1.85, cy_row - 1.85], color='#1E8449', lw=0.6, alpha=0.5) # ══════════════════════════════════════════════════════════════════════════════ # COMPARISON ARROW (middle connector) # ══════════════════════════════════════════════════════════════════════════════ ax.annotate('', xy=(10.35, 17.5), xytext=(9.65, 17.5), arrowprops=dict(arrowstyle='<->', color=C['accent1'], lw=2.5)) ax.text(10.0, 17.65, 'Compare', ha='center', va='bottom', fontsize=8, color=C['accent1'], style='italic') # ══════════════════════════════════════════════════════════════════════════════ # BOTTOM NOTE # ══════════════════════════════════════════════════════════════════════════════ note_box = FancyBboxPatch((0.2, 1.65), 19.6, 0.9, boxstyle="round,pad=0.1", facecolor='#1A2F47', edgecolor=C['accent3'], lw=1.2) ax.add_patch(note_box) ax.text(10, 2.12, 'KEY: OCT is the investigation of choice for staging (replaces FFA). ' 'Watzke-Allen test: beam thinned/broken = FTMH. ' 'Ocriplasmin for VMT/small FTMH.', ha='center', va='center', fontsize=9, color=C['text']) # Source ax.text(10, 0.5, 'Source: Kanski\'s Clinical Ophthalmology 10th Ed. pp. 611–629 · Wills Eye Manual · IVTS Group Classification', ha='center', va='center', fontsize=8.5, color=C['subtext'], style='italic') plt.savefig('/tmp/workspace/ffa-pdf/Macular_Hole_Staging.png', dpi=150, bbox_inches='tight', facecolor='#0D1B2A') plt.close() print("Done") PYEOF
ls -lh /tmp/workspace/ffa-pdf/Macular_Hole_Staging.png
Macular Hole Staging
Image · PNG
Generate a PDF combining all macular hole diagrams and reference text
ls -lh /tmp/workspace/ffa-pdf/
python3 << 'PYEOF' from reportlab.lib.pagesizes import A4 from reportlab.lib import colors from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle from reportlab.lib.units import cm, mm from reportlab.platypus import (SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, HRFlowable, KeepTogether, PageBreak) from reportlab.platypus import Image as RLImage from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY OUTPUT = "/tmp/workspace/ffa-pdf/Macular_Hole_Complete_Reference.pdf" W, H = A4 doc = SimpleDocTemplate( OUTPUT, pagesize=A4, leftMargin=1.8*cm, rightMargin=1.8*cm, topMargin=1.8*cm, bottomMargin=1.8*cm, title="Macular Hole – Complete Reference", author="Kanski 10th Ed / Wills Eye Manual" ) CW = W - 3.6*cm # content width # ── Colours ────────────────────────────────────────────────────────────────── DARK = colors.HexColor("#0D1B2A") NAVY = colors.HexColor("#1A3A5C") BLUE = colors.HexColor("#2E6DA4") LBLUE = colors.HexColor("#D6E8F7") TEAL = colors.HexColor("#17A589") TEAL_L = colors.HexColor("#D0F0EB") ORANGE = colors.HexColor("#E67E22") ORANGE_L = colors.HexColor("#FDE9D1") RED = colors.HexColor("#C0392B") RED_L = colors.HexColor("#FDEDEC") PURPLE = colors.HexColor("#7D3C98") PURPLE_L = colors.HexColor("#F4ECF7") GREEN = colors.HexColor("#1E8449") GREEN_L = colors.HexColor("#E9F7EF") YELLOW = colors.HexColor("#B7950B") YELLOW_L = colors.HexColor("#FEF9E7") GREY = colors.HexColor("#566573") GREY_L = colors.HexColor("#F2F3F4") WHITE = colors.white BLACK = colors.HexColor("#1C2833") # ── Styles ─────────────────────────────────────────────────────────────────── def S(name, **kw): base = kw.pop('base', 'Normal') d = dict(fontName='Helvetica', fontSize=10, leading=14, textColor=BLACK, spaceAfter=3) d.update(kw) return ParagraphStyle(name, **d) title_s = S('T', fontName='Helvetica-Bold', fontSize=20, textColor=WHITE, alignment=TA_CENTER, spaceAfter=2, leading=26) sub_s = S('Sub',fontName='Helvetica', fontSize=11, textColor=colors.HexColor("#AED6F1"), alignment=TA_CENTER) src_s = S('Src',fontName='Helvetica-Oblique', fontSize=8.5, textColor=colors.HexColor("#85C1E9"), alignment=TA_CENTER) h1_s = S('H1', fontName='Helvetica-Bold', fontSize=13, textColor=WHITE, leading=17, leftIndent=6) h2_s = S('H2', fontName='Helvetica-Bold', fontSize=11, textColor=NAVY, spaceBefore=5, spaceAfter=2) body_s = S('Bo', fontSize=9.5, leading=14, alignment=TA_JUSTIFY) bullet_s = S('Bu', fontSize=9.5, leading=14, leftIndent=12) tip_s = S('Ti', fontName='Helvetica-Oblique', fontSize=9.5, textColor=TEAL, leftIndent=8, leading=13) cell_h = S('CH', fontName='Helvetica-Bold', fontSize=9.5, textColor=WHITE, alignment=TA_CENTER, leading=13) cell_b = S('CB', fontName='Helvetica-Bold', fontSize=9, textColor=NAVY, leading=12) cell_n = S('CN', fontSize=9, leading=12) mcq_a = S('MA', fontName='Helvetica-Bold', fontSize=9, textColor=RED, leading=12) small_s = S('Sm', fontSize=8, textColor=GREY, alignment=TA_CENTER, leading=11) def sec_hdr(txt, bg=BLUE): t = Table([[Paragraph(txt, h1_s)]], colWidths=[CW]) t.setStyle(TableStyle([ ('BACKGROUND',(0,0),(-1,-1), bg), ('TOPPADDING',(0,0),(-1,-1), 7),('BOTTOMPADDING',(0,0),(-1,-1), 7), ('LEFTPADDING',(0,0),(-1,-1), 10), ('BOX',(0,0),(-1,-1), 0.6, colors.HexColor("#0D2744")), ])) return t def hr(c=BLUE, t=0.8): return HRFlowable(width='100%', thickness=t, color=c, spaceAfter=4, spaceBefore=4) def grid_table(data, cols, hdr_bg=BLUE, alt1=LBLUE, alt2=WHITE, bold_col0=True): formatted = [] for ri, row in enumerate(data): frow = [] for ci, cell in enumerate(row): if ri == 0: frow.append(Paragraph(str(cell), cell_h)) elif ci == 0 and bold_col0: frow.append(Paragraph(str(cell), cell_b)) else: frow.append(Paragraph(str(cell), cell_n)) formatted.append(frow) t = Table(formatted, colWidths=cols, repeatRows=1) ts = [ ('BACKGROUND',(0,0),(-1,0), hdr_bg), ('GRID',(0,0),(-1,-1), 0.4, colors.HexColor("#B0C4DE")), ('BOX',(0,0),(-1,-1), 0.8, hdr_bg), ('VALIGN',(0,0),(-1,-1),'MIDDLE'), ('TOPPADDING',(0,0),(-1,-1), 5),('BOTTOMPADDING',(0,0),(-1,-1), 5), ('LEFTPADDING',(0,0),(-1,-1), 5), ] for i in range(1, len(data)): ts.append(('BACKGROUND',(0,i),(-1,i), alt1 if i%2==1 else alt2)) t.setStyle(TableStyle(ts)) return t def mcq_table(data): fmt = [[Paragraph('MCQ POINT', cell_h), Paragraph('ANSWER', cell_h)]] for q, a in data: fmt.append([Paragraph(q, cell_n), Paragraph(a, mcq_a)]) t = Table(fmt, colWidths=[CW*0.58, CW*0.42], repeatRows=1) ts = [ ('BACKGROUND',(0,0),(-1,0), RED), ('GRID',(0,0),(-1,-1), 0.4, colors.HexColor("#F1948A")), ('BOX',(0,0),(-1,-1), 0.8, RED), ('VALIGN',(0,0),(-1,-1),'MIDDLE'), ('TOPPADDING',(0,0),(-1,-1), 5),('BOTTOMPADDING',(0,0),(-1,-1), 5), ('LEFTPADDING',(0,0),(-1,-1), 5), ] for i in range(1, len(fmt)): ts.append(('BACKGROUND',(0,i),(-1,i), RED_L if i%2==1 else WHITE)) t.setStyle(TableStyle(ts)) return t def info_box(text, bg=YELLOW_L, border=YELLOW): t = Table([[Paragraph(text, tip_s)]], colWidths=[CW]) t.setStyle(TableStyle([ ('BACKGROUND',(0,0),(-1,-1), bg), ('BOX',(0,0),(-1,-1), 1, border), ('TOPPADDING',(0,0),(-1,-1), 6),('BOTTOMPADDING',(0,0),(-1,-1), 6), ('LEFTPADDING',(0,0),(-1,-1), 10), ])) return t # ════════════════════════════════════════════════════════════════════════════ story = [] # ── PAGE 1: TITLE ───────────────────────────────────────────────────────── title_tbl = Table([ [Paragraph('MACULAR HOLE', title_s)], [Paragraph('Complete PG Ophthalmology Reference', sub_s)], [Paragraph('Kanski\'s Clinical Ophthalmology 10th Ed. (pp. 611–629) · Wills Eye Manual (pp. 867–869)', src_s)], ], colWidths=[CW]) title_tbl.setStyle(TableStyle([ ('BACKGROUND',(0,0),(-1,-1), DARK), ('TOPPADDING',(0,0),(-1,-1), 9),('BOTTOMPADDING',(0,0),(-1,-1), 9), ('LEFTPADDING',(0,0),(-1,-1), 10), ('BOX',(0,0),(-1,-1), 1.2, BLUE), ])) story.append(title_tbl) story.append(Spacer(1, 5*mm)) # ── SECTION 1: OVERVIEW ─────────────────────────────────────────────────── story.append(sec_hdr('1. OVERVIEW & EPIDEMIOLOGY')) story.append(Spacer(1, 3*mm)) overview = [ ['Parameter', 'Detail'], ['Definition', 'Full-thickness dehiscence of ALL retinal layers (ILM → RPE) at the fovea'], ['Prevalence', '~3 per 1000'], ['Peak age/sex', 'Females aged 60–70 years (most common)'], ['Bilaterality', '~30% bilateral at presentation'], ['Fellow eye risk', '~10% within 5 years (reduced if PVD already present)'], ['Primary mechanism', 'Vitreomacular traction (VMT) — anomalous PVD'], ['Other causes', 'High myopia, blunt trauma, CME'], ] story.append(grid_table(overview, [3.5*cm, CW-3.5*cm], hdr_bg=NAVY, alt1=LBLUE)) story.append(Spacer(1, 4*mm)) # ── SECTION 2: GASS CLASSIFICATION ─────────────────────────────────────── story.append(sec_hdr('2. GASS CLASSIFICATION (Clinical / Ophthalmoscopy)', bg=colors.HexColor("#1A5276"))) story.append(Spacer(1, 3*mm)) story.append(Paragraph( 'The original Gass staging is based on careful clinical examination and is still widely used in PG examinations.', body_s)) story.append(Spacer(1, 2*mm)) gass_data = [ ['Stage', 'Name', 'Size', 'Key Features'], ['1a', 'Impending hole\n(VMT)', '—', 'Yellow spot at fovea · Foveal flattening · VMT present · Inner–outer retina split · Posterior hyaloid attached'], ['1b', 'Occult hole\n(VMA)', '—', 'Yellow RING at fovea · Foveal detachment · Cystic schisis cavity · VMT present'], ['2', 'Early FTMH', '<400 µm', 'Small full-thickness hole · Central/eccentric/crescent · Posterior hyaloid STILL attached · Operculum visible · Progresses in nearly all'], ['3', 'FTMH, no PVD', '≥400 µm', 'Full-thickness hole · Cuff of SRF · NO PVD (hyaloid attached) · Free-floating operculum'], ['4', 'FTMH + PVD', '≥400 µm', 'Full-thickness hole · Cuff of SRF · Complete PVD present · Weiss ring visible'], ] story.append(grid_table(gass_data, [1.2*cm, 2.8*cm, 2.0*cm, CW-6.0*cm], hdr_bg=colors.HexColor("#1A5276"), alt1=LBLUE)) story.append(Spacer(1, 3*mm)) story.append(info_box( 'TIP: Stage 1 = no true hole (impending); Stage 2 = first true FTMH (<400 µm); ' 'Stages 3 & 4 differ ONLY in PVD status. ~50% of Stage 1 resolve spontaneously.', bg=LBLUE, border=BLUE)) story.append(Spacer(1, 4*mm)) # ── SECTION 3: IVTS CLASSIFICATION ─────────────────────────────────────── story.append(sec_hdr('3. IVTS CLASSIFICATION (OCT-based — Current Standard)', bg=GREEN)) story.append(Spacer(1, 3*mm)) story.append(Paragraph( 'The International Vitreomacular Traction Study (IVTS) Group classification uses OCT measurements ' 'and replaces Gass staging as the contemporary standard. Size is measured at the <b>narrowest point</b> on OCT.', body_s)) story.append(Spacer(1, 2*mm)) ivts_data = [ ['IVTS Category', 'Size', 'VMT?', 'Gass Equiv.', 'Key Features'], ['VMA\n(Vitreomacular Adhesion)', '—', 'Attached\nno distortion', '—', 'Vitreous within 3 mm · Focal ≤1500 µm; Broad >1500 µm · No foveal changes · Usually resolves'], ['VMT\n(Vitreomacular Traction)', '—', 'Present +\ndistortion', '1a / 1b', 'Foveal contour distorted on OCT · No FTMH · May be isolated or concurrent (AMD, RVO, DR)'], ['Small FTMH + VMT', '<250 µm', 'Present', '2 (small)', 'Full-thickness hole · VMT present · Best surgical outcome'], ['Medium FTMH + VMT', '250–400 µm', 'Present', '2 (medium)', 'Full-thickness hole · VMT present · Good surgical outcome'], ['Large FTMH', '>400 µm', '± Present', '3 / 4', 'Full-thickness hole · With or without VMT · Poorest prognosis for closure'], ] story.append(grid_table(ivts_data, [3.0*cm, 1.5*cm, 1.8*cm, 2.0*cm, CW-8.3*cm], hdr_bg=GREEN, alt1=GREEN_L)) story.append(Spacer(1, 4*mm)) # ── SECTION 4: STAGING DIAGRAM ──────────────────────────────────────────── story.append(sec_hdr('4. STAGING DIAGRAM (Gass vs IVTS)', bg=PURPLE)) story.append(Spacer(1, 3*mm)) try: img = RLImage('/tmp/workspace/ffa-pdf/Macular_Hole_Staging.png', width=CW, height=CW * (26/20)) story.append(img) story.append(Paragraph( 'Fig: Retinal cross-section diagrams for each Gass stage (left) and IVTS category (right). ' 'RPE = brown; neurosensory retina = tan; SRF = blue; VMT traction = red; ' 'posterior hyaloid = blue line (solid = attached, dashed = partial).', small_s)) except Exception as e: story.append(Paragraph(f'[Diagram unavailable: {e}]', body_s)) story.append(Spacer(1, 4*mm)) # ── PAGE BREAK ──────────────────────────────────────────────────────────── story.append(PageBreak()) # ── SECTION 5: SYMPTOMS ─────────────────────────────────────────────────── story.append(sec_hdr('5. SYMPTOMS', bg=ORANGE)) story.append(Spacer(1, 3*mm)) for sym in [ '• <b>Central visual loss</b> — impaired central vision, may be first noticed when fellow eye occluded', '• <b>Metamorphopsia</b> — distortion of central vision', '• <b>Asymptomatic in early stages</b> (Stage 1) — may be found at routine sight test', '• Symptoms absent or mild before full-thickness lesion develops', ]: story.append(Paragraph(sym, bullet_s)) story.append(Spacer(1, 4*mm)) # ── SECTION 6: INVESTIGATIONS ───────────────────────────────────────────── story.append(sec_hdr('6. INVESTIGATIONS', bg=NAVY)) story.append(Spacer(1, 3*mm)) inv_data = [ ['Test', 'Finding in Macular Hole', 'Notes'], ['Amsler Grid', 'Non-specific central distortion (not a scotoma)', 'Poor specificity; does not distinguish from other macular pathology'], ['Watzke–Allen Test', 'Narrow slit beam appears THINNED or BROKEN', 'Pseudohole/other: distorted beam of uniform thickness. Use fundus contact lens.'], ['OCT\n(KEY investigation)', 'Full-thickness defect; dynamic staging; cystic changes; VMT; operculum', 'Investigation of choice for diagnosis AND staging. FFA NOT needed.'], ['FFA', 'Early well-defined WINDOW DEFECT (hyperfluorescence)', 'OCT has replaced FFA — no longer routinely required for macular hole'], ['FAF\n(Fundus Autofluorescence)', 'Stage 2: punctate fluorescence\nStages 3–4: hyperfluorescent foveolar spot', 'Useful for monitoring; not diagnostic alone'], ] story.append(grid_table(inv_data, [2.5*cm, 5.5*cm, CW-8.0*cm], hdr_bg=NAVY, alt1=LBLUE)) story.append(Spacer(1, 3*mm)) story.append(info_box( 'TIP (Kanski): OCT is key to confirmation of diagnosis and staging of macular hole. ' 'FFA is NOT required.', bg=LBLUE, border=BLUE)) story.append(Spacer(1, 4*mm)) # ── SECTION 7: DIFFERENTIAL DIAGNOSIS ──────────────────────────────────── story.append(sec_hdr('7. DIFFERENTIAL DIAGNOSIS', bg=TEAL)) story.append(Spacer(1, 3*mm)) dd_data = [ ['Condition', 'Key Distinguishing Feature'], ['Pseudohole (ERM/macular pucker)', 'No loss of foveal tissue · ERM/ILM sheen · Watzke-Allen NEGATIVE · OCT: intact retina depth'], ['Lamellar hole', 'Partial thickness only · Less red · No surrounding grey halo · OCT: intact outer retina'], ['CME (Cystoid macular oedema)', 'Intraretinal cysts · No FTMH · Underlying cause (uveitis, post-op, DR)'], ['Solar retinopathy', 'Small yellow/red foveal lesion + fine grey pigment · History of sun gazing'], ['CSCR', 'Subretinal fluid · Younger males · Neurosensory detachment on OCT'], ['Adult vitelliform dystrophy', 'Bilateral · Yellowish subfoveal material · Abnormal ERG/EOG'], ['Subfoveal drusen', 'Scattered yellow deposits · No full-thickness defect'], ] story.append(grid_table(dd_data, [4.5*cm, CW-4.5*cm], hdr_bg=TEAL, alt1=TEAL_L)) story.append(Spacer(1, 4*mm)) # ── SECTION 8: TREATMENT ───────────────────────────────────────────────── story.append(sec_hdr('8. TREATMENT', bg=colors.HexColor("#6C3483"))) story.append(Spacer(1, 3*mm)) # 8a: Summary table tx_data = [ ['Stage', 'Management', 'Notes'], ['Stage 1 / VMA / VMT', 'OBSERVATION', '~50% resolve with spontaneous vitreo-foveolar separation\nMonitor with OCT'], ['VMT / small FTMH', 'OCRIPLASMIN\n(Pharmacological vitreolysis)', 'Intravitreal injection · Dissolves fibronectin + laminin at VMT site\nSuitable for small early-stage holes'], ['Stage 2 or greater\n(FTMH ≥Stage 2)', 'SURGERY\n(Pars plana vitrectomy)', 'Surgery considered for Stage 2 or greater\nShorter duration + smaller size = better outcome'], ['Intravitreal gas/air', 'Emerging alternative', 'Preliminary evidence of closure after small intravitreal gas bolus\nAvoiding vitrectomy in selected cases'], ] story.append(grid_table(tx_data, [3.2*cm, 3.5*cm, CW-6.7*cm], hdr_bg=PURPLE, alt1=PURPLE_L)) story.append(Spacer(1, 3*mm)) # 8b: Surgery detail story.append(Paragraph('<b>Surgical Technique — Pars Plana Vitrectomy:</b>', h2_s)) surg_steps = [ ('Step 1 — Vitrectomy', 'Remove vitreous gel + induce total PVD if not already present → relieves VMT'), ('Step 2 — ILM Peeling', 'Internal limiting membrane removed facilitated by vital dye staining ' '(e.g. Brilliant Blue G, Indocyanine Green)'), ('Step 3 — Gas Tamponade', 'SF₆ or C₃F₈ gas injected to tamponade the hole margins'), ] surg_data = [['Step', 'Action']] + [[s, d] for s, d in surg_steps] story.append(grid_table(surg_data, [3.8*cm, CW-3.8*cm], hdr_bg=PURPLE, alt1=PURPLE_L)) story.append(Spacer(1, 3*mm)) story.append(Paragraph('<b>Post-operative Positioning:</b>', h2_s)) story.append(Paragraph( 'Face-down positioning: <b>8 hours per day for 5 days</b>. ' 'Extensive positioning not required with modern techniques.', body_s)) story.append(Spacer(1, 3*mm)) # 8c: Outcomes story.append(Paragraph('<b>Surgical Outcomes:</b>', h2_s)) out_data = [ ['Outcome Measure', 'Result'], ['Hole closure rate', 'Most cases (high anatomical success)'], ['Visual improvement (over months)', '80–90% of eyes'], ['Final VA ≥ 6/12', '~65%'], ['Visual worsening', 'Up to 10%'], ['Residual OCT abnormality', 'Common — IS/OS junction defect (diagnostic of spontaneously healed microhole)'], ['Best predictors of success', 'Smaller hole + duration < 6 months before surgery'], ] story.append(grid_table(out_data, [5.0*cm, CW-5.0*cm], hdr_bg=PURPLE, alt1=PURPLE_L)) story.append(Spacer(1, 4*mm)) # ── SECTION 9: VMT ENTITY ───────────────────────────────────────────────── story.append(sec_hdr('9. VITREOMACULAR TRACTION (VMT) — Related Entity', bg=TEAL)) story.append(Spacer(1, 3*mm)) vmt_data = [ ['Term', 'Definition', 'Subtypes'], ['VMA\n(Vitreomacular Adhesion)', 'Vitreous attached within 3 mm of central macula with perifoveal PVD; ' 'NO foveal distortion or retinal changes. Dynamic — may resolve.', 'Focal ≤1500 µm\nBroad >1500 µm'], ['VMT\n(Vitreomacular Traction)', 'VMA + foveal distortion or structural retinal changes on OCT. ' 'May be isolated or concurrent with AMD, RVO, DR.', 'Focal ≤1500 µm\nBroad >1500 µm'], ] story.append(grid_table(vmt_data, [2.5*cm, CW*0.58, CW*0.25], hdr_bg=TEAL, alt1=TEAL_L)) story.append(Spacer(1, 4*mm)) # ── SECTION 10: MCQ ─────────────────────────────────────────────────────── story.append(sec_hdr('10. HIGH-YIELD PG MCQ POINTS', bg=RED)) story.append(Spacer(1, 3*mm)) mcqs = [ ('Most common age/sex', 'Females, 60–70 years'), ('Bilateral at presentation', '~30%'), ('Risk to fellow eye (5 years)', '~10% (less if PVD already present)'), ('Primary mechanism', 'Vitreomacular traction (VMT) — anomalous PVD'), ('Stage 1 spontaneous resolution', '~50%'), ('Full-thickness hole spontaneous closure', '~10%'), ('Investigation of choice', 'OCT (not FFA)'), ('Watzke-Allen positive finding', 'Beam THINNED or BROKEN'), ('FFA finding in FTMH', 'Early well-defined window defect'), ('FAF in Stages 3/4', 'Hyperfluorescent foveolar spot'), ('Surgery indication', 'Stage 2 or greater (FTMH)'), ('Surgery: 3 key steps', 'Vitrectomy + ILM peeling + gas tamponade'), ('ILM staining dye', 'Brilliant Blue G / Indocyanine Green (ICG)'), ('Post-op positioning', 'Face-down 8 hrs/day × 5 days'), ('VA improvement rate post-surgery', '80–90% of eyes'), ('Final VA ≥ 6/12 post-surgery', '~65%'), ('Best surgical outcome predictors', 'Smaller hole + <6 months duration'), ('Ocriplasmin mechanism', 'Dissolves fibronectin + laminin at VMT site'), ('IVTS size: Small FTMH', '<250 µm'), ('IVTS size: Medium FTMH', '250–400 µm'), ('IVTS size: Large FTMH', '>400 µm'), ('IVTS measurement point', 'Narrowest point on OCT'), ('Gass Stage 3 vs Stage 4', 'Stage 3 = no PVD; Stage 4 = complete PVD (Weiss ring)'), ('Pseudohole vs FTMH (Watzke-Allen)', 'Pseudohole: uniform distorted beam — NOT thinned/broken'), ('Lamellar hole = ', 'Partial thickness (NOT full-thickness macular hole)'), ] story.append(mcq_table(mcqs)) story.append(Spacer(1, 5*mm)) # ── FOOTER ──────────────────────────────────────────────────────────────── story.append(hr(c=GREY, t=0.5)) story.append(Paragraph( 'Reference: Kanski\'s Clinical Ophthalmology 10th Ed. pp. 611–629 · ' 'Wills Eye Manual pp. 867–869 · IVTS Group Classification', small_s)) doc.build(story) print("PDF done:", OUTPUT) PYEOF
ls -lh /tmp/workspace/ffa-pdf/
Macular Hole Complete Reference
PDF Document · PDF
| Section | Content |
|---|---|
| 1. Overview & Epidemiology | Prevalence, age/sex, bilaterality, mechanism |
| 2. Gass Classification | All 5 stages (Normal + 1a/1b/2/3/4) with sizes and key features |
| 3. IVTS Classification | VMA → VMT → Small/Medium/Large FTMH with Gass equivalents |
| 4. Staging Diagram | Full embedded cross-section diagram (Gass vs IVTS side-by-side) |
| 5. Symptoms | Central loss, metamorphopsia, asymptomatic early stages |
| 6. Investigations | Amsler, Watzke-Allen, OCT, FFA, FAF — with findings per test |
| 7. Differential Diagnosis | 7 conditions vs FTMH with distinguishing features |
| 8. Treatment | Table by stage + surgical steps (vitrectomy/ILM peeling/gas) + outcomes |
| 9. VMT Entity | VMA vs VMT definitions with focal/broad subtypes |
| 10. MCQ Points | 25 high-yield Q&A pairs in red-highlighted table |
Generate a visual guide to OCT interpretation in macular holes (ILM, cysts, operculum)
python3 << 'PYEOF' import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt import matplotlib.patches as mpatches from matplotlib.patches import FancyBboxPatch, FancyArrowPatch from matplotlib.lines import Line2D import numpy as np # ══════════════════════════════════════════════════════════════════ # CANVAS # ══════════════════════════════════════════════════════════════════ fig = plt.figure(figsize=(22, 30), facecolor='#0A1628') ax_main = fig.add_axes([0, 0, 1, 1]) ax_main.set_xlim(0, 22) ax_main.set_ylim(0, 30) ax_main.axis('off') # ── Palette ────────────────────────────────────────────────────── BG = '#0A1628' PANEL = '#112240' PANEL2 = '#0D1B36' ACCENT1 = '#4FC3F7' # sky blue ACCENT2 = '#26A69A' # teal ACCENT3 = '#FFA726' # amber ACCENT4 = '#AB47BC' # purple ACCENT5 = '#EF5350' # red GREEN_A = '#66BB6A' WHITE = '#ECEFF4' SUBTEXT = '#90A4AE' DARK_TXT = '#1A2744' # Layer colours (OCT-like) ILM_C = '#80DEEA' # cyan — ILM NFL_C = '#4FC3F7' # bright blue — NFL/GCL IPL_C = '#1565C0' # deep blue — IPL INL_C = '#283593' # indigo — INL OPL_C = '#4527A0' # purple — OPL ONL_C = '#6A1B9A' # deep purple — ONL/Henle ELM_C = '#AD1457' # pink — ELM IS_OS_C = '#E91E63' # bright pink — IS/OS junction OS_C = '#F48FB1' # light pink — OS RPE_C = '#BF360C' # dark orange-red — RPE CHOROID_C = '#4E342E' # brown — choroid VITREOUS_C = '#1A3A5C' # dark navy — vitreous HYALOID_C = '#29B6F6' # blue — posterior hyaloid SRF_C = '#4DD0E1' # cyan — SRF CYST_C = '#0097A7' # teal — cysts/schisis OPERCULUM_C = '#FFB300' # gold — operculum VMT_C = '#F44336' # red — VMT line HOLE_C = '#020810' # near-black — hole # ══════════════════════════════════════════════════════════════════ # HELPERS # ══════════════════════════════════════════════════════════════════ def rounded_box(ax, x, y, w, h, fc, ec=None, lw=1.2, alpha=1.0, radius=0.15): box = FancyBboxPatch((x, y), w, h, boxstyle=f"round,pad={radius}", facecolor=fc, edgecolor=ec if ec else fc, linewidth=lw, alpha=alpha, zorder=2) ax.add_patch(box) def label(ax, x, y, txt, fs=9, c=WHITE, bold=False, align='left', va='center', zorder=8): fw = 'bold' if bold else 'normal' ax.text(x, y, txt, fontsize=fs, color=c, fontweight=fw, ha=align, va=va, zorder=zorder) def arrow(ax, x1, y1, x2, y2, c=ACCENT1, lw=1.4, style='->', head=8): ax.annotate('', xy=(x2, y2), xytext=(x1, y1), arrowprops=dict(arrowstyle=f'->', color=c, lw=lw, mutation_scale=head), zorder=7) def bracket_label(ax, bx, by1, by2, label_txt, side='right', c=ACCENT1, fs=8): """Draw a vertical bracket with label.""" bx2 = bx + 0.18 if side == 'right' else bx - 0.18 ax.plot([bx, bx2, bx2, bx], [by1, by1, by2, by2], color=c, lw=1.2, zorder=6) mx = (by1 + by2) / 2 tx = bx2 + 0.12 if side == 'right' else bx2 - 0.12 ha = 'left' if side == 'right' else 'right' ax.text(tx, mx, label_txt, fontsize=fs, color=c, ha=ha, va='center', zorder=7) # ══════════════════════════════════════════════════════════════════ # TITLE # ══════════════════════════════════════════════════════════════════ rounded_box(ax_main, 0.3, 28.4, 21.4, 1.35, fc=PANEL, ec=ACCENT1, lw=2) label(ax_main, 11, 29.22, 'OCT INTERPRETATION GUIDE — MACULAR HOLE', fs=20, c=WHITE, bold=True, align='center') label(ax_main, 11, 28.72, 'ILM · Retinal Layers · Cysts · Operculum · SRF · VMT | Kanski 10th Ed. pp. 611–629', fs=11, c=ACCENT1, align='center') # ══════════════════════════════════════════════════════════════════ # SECTION A: LAYER LEGEND (top right panel) # ══════════════════════════════════════════════════════════════════ rounded_box(ax_main, 15.2, 22.3, 6.5, 5.8, fc=PANEL, ec=ACCENT2, lw=1.5) label(ax_main, 18.45, 27.85, 'RETINAL LAYERS ON OCT', fs=11, c=ACCENT2, bold=True, align='center') label(ax_main, 18.45, 27.5, '(posterior to vitreous → RPE)', fs=8.5, c=SUBTEXT, align='center') layers = [ (ILM_C, 'ILM', 'Internal Limiting Membrane — thin bright line at surface'), (NFL_C, 'NFL', 'Nerve Fibre Layer (hyperreflective)'), (IPL_C, 'IPL', 'Inner Plexiform Layer'), (INL_C, 'INL', 'Inner Nuclear Layer'), (OPL_C, 'OPL', 'Outer Plexiform Layer (Henle fibres)'), (ONL_C, 'ONL', 'Outer Nuclear Layer'), (ELM_C, 'ELM', 'External Limiting Membrane'), (IS_OS_C, 'IS/OS', 'Inner/Outer Segment junction (ellipsoid zone) — bright line'), (OS_C, 'OS', 'Outer Segments of photoreceptors'), (RPE_C, 'RPE', 'Retinal Pigment Epithelium — bright hyperreflective band'), (CHOROID_C, 'Choriod','Choroid (hyporeflective with vessel shadows)'), ] ly = 27.1 for col, abbr, desc in layers: rounded_box(ax_main, 15.4, ly - 0.22, 0.55, 0.38, fc=col, ec='none', alpha=0.95) label(ax_main, 16.1, ly, f'<{abbr}>', fs=8.2, c=col, bold=True) label(ax_main, 17.0, ly, desc, fs=7.8, c=SUBTEXT) ly -= 0.44 # Special structures legend ly -= 0.1 spec = [ (HYALOID_C, 'Posterior hyaloid face'), (VMT_C, 'VMT traction force'), (CYST_C, 'Intraretinal cyst / schisis'), (SRF_C, 'Subretinal fluid (SRF)'), (OPERCULUM_C, 'Operculum (avulsed ILM/retina)'), ] for col, desc in spec: rounded_box(ax_main, 15.4, ly - 0.22, 0.55, 0.38, fc=col, ec='none', alpha=0.9) label(ax_main, 16.1, ly, desc, fs=7.8, c=col) ly -= 0.4 # ══════════════════════════════════════════════════════════════════ # CORE OCT CROSS-SECTION DRAWING FUNCTION # ══════════════════════════════════════════════════════════════════ def draw_oct(ax, cx, base_y, variant='normal', scale=1.0): """ Draw a high-detail OCT cross-section. base_y = y of RPE top surface. cx = horizontal centre. scale = horizontal width multiplier. variant: 'normal','vma','vmt','stage1b','stage2','stage3','stage4', 'lamellar','resolved','operculum_detail' """ W = 3.0 * scale # half-width of scan xl = cx - W xr = cx + W xs = np.linspace(xl, xr, 300) xn = (xs - cx) / W # normalized -1..1 # ── Layer thicknesses (approx, in plot units) ── T = { 'choroid': 0.55, 'rpe': 0.12, 'os': 0.13, 'isOS': 0.04, 'onl': 0.28, 'elm': 0.03, 'opl': 0.10, 'inl': 0.12, 'ipl': 0.14, 'nfl': 0.10, 'ilm': 0.03, } # Normal foveal profile: dip at centre def foveal_profile(xn_arr, depth=0.18, width=0.35): return depth * np.exp(-(xn_arr**2) / (width**2)) # ── Build layer boundaries from RPE upward ── rpe_bot = base_y rpe_top = rpe_bot + T['rpe'] os_top = rpe_top + T['os'] iso_top = os_top + T['isOS'] # ONL varies with foveal dip (thicker at fovea — Henle fibres) onl_thick = T['onl'] + 0.10 * np.exp(-(xn**2)/0.18) elm_bot = iso_top + 0.02 elm_top = elm_bot + T['elm'] onl_bot = elm_top onl_top = onl_bot + onl_thick opl_top = onl_top + T['opl'] inl_top = opl_top + T['inl'] ipl_top = inl_top + T['ipl'] # NFL/GCL — thin at fovea, thicker peripherally nfl_thick = T['nfl'] * (0.3 + 0.7 * xn**2) nfl_top = ipl_top + nfl_thick # ILM = surface of retina; dip at fovea fov_dip = foveal_profile(xn, depth=0.20, width=0.30) ilm_surf = nfl_top - fov_dip # Vitreous roof (flat, above retina) vit_top = base_y + 1.85 # ── Helper: fill a band between two y-arrays ── def fill_band(ax, xs, y_bot, y_top, color, alpha=0.88, zorder=3): if np.isscalar(y_bot): y_bot = np.full_like(xs, y_bot) if np.isscalar(y_top): y_top = np.full_like(xs, y_top) ax.fill_between(xs, y_bot, y_top, color=color, alpha=alpha, zorder=zorder) # ── VARIANTS — modify geometry before drawing ── hole_mask = np.ones(len(xs), dtype=bool) # True = draw retina cyst_mask = np.zeros(len(xs), dtype=bool) srf_mask = np.zeros(len(xs), dtype=bool) vmt_line = False hyaloid_y = None hyaloid_style = '-' operculum = False operculum_y = base_y + 1.55 lamellar = False lamellar_depth = 0.0 resolved_defect = False schisis = False schisis_depth = 0.0 weiss_ring = False hole_hw = 0.0 # hole half-width if variant == 'vma': # Vitreous attached at small foveal area — green attachment dot hyaloid_y = np.full_like(xs, vit_top - 0.15) # attachment region att = np.abs(xn) < 0.15 hyaloid_y[att] = ilm_surf[att] + 0.01 hyaloid_style = '-' elif variant == 'vmt': # V-shaped traction on fovea hyaloid_y = np.full_like(xs, vit_top - 0.05) pull = 0.25 * np.exp(-(xn**2)/0.12) hyaloid_y -= pull vmt_line = True # Fovea pulled upward (elevation instead of dip) fov_dip *= 0.2 ilm_surf = nfl_top - fov_dip schisis = True schisis_depth = 0.10 elif variant == 'stage1b': # Foveal detachment + cystic cavity hyaloid_y = np.full_like(xs, vit_top - 0.05) pull = 0.22 * np.exp(-(xn**2)/0.1) hyaloid_y -= pull vmt_line = True schisis = True schisis_depth = 0.15 fov_dip *= 0.1 ilm_surf = nfl_top - fov_dip elif variant == 'stage2': hole_hw = 0.22 hole_mask = np.abs(xn) > (hole_hw / 1.0) srf_mask = np.abs(xn) < (hole_hw * 1.6) operculum = True operculum_y = base_y + 1.45 hyaloid_y = np.full_like(xs, vit_top - 0.05) pull = 0.15 * np.exp(-(xn**2)/0.06) hyaloid_y -= pull vmt_line = True elif variant == 'stage3': hole_hw = 0.45 hole_mask = np.abs(xn) > hole_hw srf_mask = np.abs(xn) < (hole_hw * 1.5) operculum = True operculum_y = base_y + 1.35 hyaloid_y = np.full_like(xs, vit_top - 0.25) hyaloid_style = '--' elif variant == 'stage4': hole_hw = 0.50 hole_mask = np.abs(xn) > hole_hw srf_mask = np.abs(xn) < (hole_hw * 1.8) operculum = True operculum_y = base_y + 1.60 hyaloid_y = np.full_like(xs, vit_top - 0.05) hyaloid_style = '-' weiss_ring = True elif variant == 'lamellar': lamellar = True lamellar_depth = 0.20 hyaloid_y = np.full_like(xs, vit_top - 0.15) elif variant == 'resolved': resolved_defect = True hyaloid_y = np.full_like(xs, vit_top - 0.15) # ── DRAW CHOROID ── ax.fill_between(xs, rpe_bot - T['choroid'], rpe_bot, color=CHOROID_C, alpha=0.75, zorder=2) # ── DRAW RPE ── ax.fill_between(xs, rpe_bot, rpe_top, color=RPE_C, alpha=0.92, zorder=3) # ── DRAW OUTER RETINAL LAYERS (if not in hole) ── # Apply hole mask xs_m = xs.copy() m = hole_mask if m.any(): # OS ax.fill_between(xs[m], rpe_top, os_top[m] if not np.isscalar(os_top) else os_top, color=OS_C, alpha=0.7, zorder=3, where=m) # IS/OS junction ax.fill_between(xs[m], os_top, iso_top, color=IS_OS_C, alpha=0.9, zorder=4, where=m) # ELM ax.fill_between(xs[m], elm_bot, elm_top, color=ELM_C, alpha=0.85, zorder=4, where=m) # ONL ax.fill_between(xs[m], onl_bot[m], onl_top[m], color=ONL_C, alpha=0.75, zorder=3, where=m) # OPL ax.fill_between(xs[m], onl_top[m], opl_top[m], color=OPL_C, alpha=0.75, zorder=3, where=m) if not lamellar: # INL ax.fill_between(xs[m], opl_top[m], inl_top[m], color=INL_C, alpha=0.75, zorder=3, where=m) # IPL ax.fill_between(xs[m], inl_top[m], ipl_top[m], color=IPL_C, alpha=0.8, zorder=3, where=m) # NFL ax.fill_between(xs[m], ipl_top[m], nfl_top[m], color=NFL_C, alpha=0.8, zorder=3, where=m) # ILM ax.plot(xs, ilm_surf, color=ILM_C, lw=2.0, zorder=5) else: # Lamellar: inner retina intact, outer scooped scoop = lamellar_depth * np.exp(-(xn**2)/0.1) ax.fill_between(xs, onl_bot, onl_bot + scoop, color=BG, alpha=1.0, zorder=4) # scoop cavity ax.fill_between(xs[m], opl_top[m], inl_top[m], color=INL_C, alpha=0.75, zorder=3, where=m) ax.fill_between(xs[m], inl_top[m], ipl_top[m], color=IPL_C, alpha=0.8, zorder=3, where=m) ax.fill_between(xs[m], ipl_top[m], nfl_top[m], color=NFL_C, alpha=0.8, zorder=3, where=m) ax.plot(xs, ilm_surf, color=ILM_C, lw=2.0, zorder=5) # ── HOLE: dark gap ── if hole_hw > 0: hm = ~hole_mask ax.fill_between(xs[hm], rpe_top, base_y + 1.5, color=HOLE_C, alpha=1.0, zorder=5, where=hm) # ── SRF ── if srf_mask.any(): srf_h = 0.12 ax.fill_between(xs[srf_mask], rpe_top, np.full(srf_mask.sum(), rpe_top + srf_h), color=SRF_C, alpha=0.55, zorder=4, where=srf_mask) # ── INTRARETINAL CYSTS / SCHISIS ── if schisis: cyst_xn = np.abs(xn) < 0.28 cyst_centre_y = onl_top[cyst_xn].mean() - schisis_depth/2 cyst_h = schisis_depth * np.exp(-(xn[cyst_xn]**2)/0.05) ax.fill_between(xs[cyst_xn], onl_top[cyst_xn] - cyst_h/2, onl_top[cyst_xn] + cyst_h/2, color=CYST_C, alpha=0.7, zorder=5) # ── OPERCULUM ── if operculum: op_hw = 0.20 + hole_hw * 0.3 op_xs = np.linspace(cx - op_hw, cx + op_hw, 60) op_ys = np.full_like(op_xs, operculum_y) # slightly curved op_ys += 0.04 * np.sin(np.pi * np.linspace(0, 1, 60)) ax.fill_between(op_xs, op_ys - 0.045, op_ys + 0.045, color=OPERCULUM_C, alpha=0.85, zorder=6) ax.plot(op_xs, op_ys + 0.045, color='#FFD54F', lw=1.2, zorder=7) ax.plot(op_xs, op_ys - 0.045, color='#E65100', lw=1.2, zorder=7) # ── POSTERIOR HYALOID ── if hyaloid_y is not None: ls = hyaloid_style ax.plot(xs, hyaloid_y, color=HYALOID_C, lw=2.2, linestyle=ls, zorder=6, alpha=0.9) # ── VMT TRACTION LINES ── if vmt_line and hyaloid_y is not None: for xoff in [-0.15, 0.0, 0.15]: xi = int((xoff / (2*W) + 0.5) * len(xs)) xi = max(0, min(len(xs)-1, xi)) ax.annotate('', xy=(xs[xi], ilm_surf[xi]), xytext=(xs[xi], hyaloid_y[xi]), arrowprops=dict(arrowstyle='->', color=VMT_C, lw=1.6, mutation_scale=7), zorder=7) # ── RESOLVED DEFECT ── if resolved_defect: # Tiny IS/OS break at fovea dm = np.abs(xn) < 0.06 ax.fill_between(xs[dm], iso_top - 0.01, iso_top + 0.05, color=BG, alpha=1.0, zorder=6, where=dm) ax.plot([cx - 0.18, cx + 0.18], [iso_top + 0.01, iso_top + 0.01], color=IS_OS_C, lw=1.5, linestyle=':', zorder=7) # ── WEISS RING ── if weiss_ring: wr_y = hyaloid_y[len(xs)//2] + 0.25 wr = mpatches.Ellipse((cx, wr_y), 0.8, 0.18, facecolor='none', edgecolor=HYALOID_C, lw=2.0, linestyle='--', zorder=7) ax.add_patch(wr) # ── VITREOUS (background fill above retina) ── ax.fill_between(xs, ilm_surf if not lamellar else ilm_surf, np.full_like(xs, base_y + 2.1), color=VITREOUS_C, alpha=0.22, zorder=1) return ilm_surf, rpe_top, iso_top, onl_top, ipl_top, nfl_top # ══════════════════════════════════════════════════════════════════ # PANEL LAYOUT # 3 columns × 4 rows of OCT panels (+ detail panel) # ══════════════════════════════════════════════════════════════════ # Panel positions: (cx, base_y, variant, title, subtitle, annotations) panels = [ # Row 1: Normal, VMA, VMT (2.8, 25.2, 'normal', 'NORMAL FOVEA', 'Reference — no pathology', [('ILM', 2.8, 27.05, 'right'), ('IS/OS', 2.8, 25.78, 'right'), ('RPE', 2.8, 25.22, 'right'), ('Foveal pit', 2.8, 26.52, 'right'), ]), (7.5, 25.2, 'vma', 'VMA', 'Vitreomacular Adhesion — no distortion', [('Posterior\nhyaloid', 7.5, 27.1, 'right'), ('Focal attachment\n≤1500 µm', 7.5, 26.62, 'right'), ('IS/OS intact', 7.5, 25.78, 'right'), ]), (12.2, 25.2, 'vmt', 'VMT', 'Vitreomacular Traction — foveal distortion', [('VMT traction', 12.2, 26.9, 'right'), ('Schisis cavity', 12.2, 26.35, 'right'), ('Elevated fovea', 12.2, 26.1, 'right'), ]), # Row 2: Stage 1b, Stage 2 FTMH (2.8, 20.5, 'stage1b', 'STAGE 1b (Occult Hole)', 'Foveal detachment + intraretinal cyst', [('VMT traction\nlines', 2.8, 22.3, 'right'), ('Intraretinal cyst\n(schisis)', 2.8, 21.55, 'right'), ('Yellow ring\n(clinical)', 2.8, 21.1, 'right'), ]), (7.5, 20.5, 'stage2', 'STAGE 2 (Early FTMH)', 'Small FTMH <400 µm + operculum', [('Operculum\n(attached hyaloid)', 7.5, 22.15, 'right'), ('VMT\nstill present', 7.5, 22.55, 'right'), ('SRF under\nhole', 7.5, 20.65, 'right'), ('FTMH\n<400 µm', 7.5, 21.3, 'right'), ]), (12.2, 20.5, 'stage3', 'STAGE 3 (FTMH, no PVD)', 'Large FTMH ≥400 µm, hyaloid attached', [('Detached hyaloid\n(dashed = no PVD)', 12.2, 22.35, 'right'), ('Free operculum', 12.2, 21.9, 'right'), ('FTMH\n≥400 µm', 12.2, 21.4, 'right'), ('SRF cuff', 12.2, 20.7, 'right'), ]), # Row 3: Stage 4, Lamellar, Resolved (2.8, 15.8, 'stage4', 'STAGE 4 (FTMH + PVD)', 'Large FTMH + complete PVD + Weiss ring', [('Weiss ring\n(PVD marker)', 2.8, 17.9, 'right'), ('Hyaloid fully\ndetached', 2.8, 17.5, 'right'), ('Operculum\n(free-floating)', 2.8, 17.0, 'right'), ('Large SRF\ncuff', 2.8, 15.95, 'right'), ]), (7.5, 15.8, 'lamellar', 'LAMELLAR HOLE', 'Partial thickness — outer retina scooped', [('ILM intact\n(key differentiator)', 7.5, 17.65, 'right'), ('Inner retina\nINTACT', 7.5, 17.15, 'right'), ('Outer retina\nscooped (partial)', 7.5, 16.4, 'right'), ('IS/OS preserved', 7.5, 15.95, 'right'), ]), (12.2, 15.8, 'resolved', 'RESOLVED / MICROHOLE', 'Spontaneous closure — residual IS/OS defect', [('ILM intact', 12.2, 17.65, 'right'), ('Tiny IS/OS\njunction defect', 12.2, 16.1, 'right'), ('Foveal contour\nrestored', 12.2, 16.75, 'right'), ]), ] # ────────────────────────────────────────────────────────────────── # SECTION HEADER BARS # ────────────────────────────────────────────────────────────────── # Row 1 header rounded_box(ax_main, 0.3, 27.85, 14.6, 0.52, fc='#0D3358', ec=ACCENT1, lw=1.2) label(ax_main, 7.6, 28.11, 'PRECURSOR STAGES (No FTMH)', fs=11, c=ACCENT1, bold=True, align='center') # Row 2 header rounded_box(ax_main, 0.3, 23.1, 14.6, 0.52, fc='#2C1654', ec=ACCENT4, lw=1.2) label(ax_main, 7.6, 23.36, 'FULL-THICKNESS MACULAR HOLE (FTMH)', fs=11, c=ACCENT4, bold=True, align='center') # Row 3 header rounded_box(ax_main, 0.3, 18.4, 14.6, 0.52, fc='#1A3D1A', ec=GREEN_A, lw=1.2) label(ax_main, 7.6, 18.66, 'DIFFERENTIAL & POST-TREATMENT', fs=11, c=GREEN_A, bold=True, align='center') # ────────────────────────────────────────────────────────────────── # DRAW ALL PANELS # ────────────────────────────────────────────────────────────────── for (cx, base_y, variant, title, subtitle, annots) in panels: # Panel background pw, ph = 4.6, 4.0 px, py = cx - pw/2, base_y - 0.3 rounded_box(ax_main, px + 0.05, py, pw - 0.1, ph, fc=PANEL2, ec=ACCENT2, lw=0.8, alpha=0.9) # Draw OCT ilm_s, rpe_t, iso_t, onl_t, ipl_t, nfl_t = draw_oct( ax_main, cx, base_y, variant=variant, scale=0.72) # Title label(ax_main, cx, base_y + 3.55, title, fs=10.5, c=WHITE, bold=True, align='center') # Subtitle label(ax_main, cx, base_y + 3.2, subtitle, fs=8.5, c=SUBTEXT, align='center') # Annotations with arrows xs_arr = np.linspace(cx - 2.16, cx + 2.16, 300) for (lbl, ax_x, ay, side) in annots: # Find closest x index xi = int((ax_x - (cx - 2.16)) / (4.32) * 299) xi = max(0, min(299, xi)) # arrow from label toward structure label(ax_main, cx - 2.0, ay, lbl, fs=7.5, c=ACCENT3, align='left', bold=False) # ══════════════════════════════════════════════════════════════════ # DETAIL INSET: OCT Features Close-Up (bottom centre) # ══════════════════════════════════════════════════════════════════ rounded_box(ax_main, 0.3, 1.5, 14.6, 13.7, fc=PANEL, ec=ACCENT3, lw=1.5) label(ax_main, 7.6, 14.85, 'DETAILED OCT FEATURES GUIDE', fs=12, c=ACCENT3, bold=True, align='center') label(ax_main, 7.6, 14.45, 'Key structures to identify on every macular hole OCT scan', fs=9, c=SUBTEXT, align='center') features = [ (ACCENT1, 'ILM (Internal Limiting Membrane)', 'Thin bright hyperreflective line at the inner retinal surface. ' 'Key landmark — peeled during macular hole surgery. ' 'Intact ILM = pseudohole/lamellar. Missing ILM = surgical or resolved hole.'), (CYST_C, 'Intraretinal Cysts / Schisis Cavity', 'Hyporeflective (dark) spaces within the retina, usually in ONL or between ' 'inner and outer retina. Earliest sign of macular hole formation (Stage 1). ' 'Distinguished from CME by location and shape.'), (OPERCULUM_C, 'Operculum', 'Avulsed fragment of inner retinal tissue (ILM ± NFL) suspended in vitreous ' 'above the hole. Appears as a small oval hyperreflective disc. ' '"Pseudo-operculum" = operculum still attached to posterior hyaloid face.'), (SRF_C, 'Subretinal Fluid (SRF) Cuff', 'Thin hyporeflective crescent surrounding the hole base, between neurosensory ' 'retina and RPE. Present in Stages 3 and 4. Width correlates with hole size. ' 'Resolves after successful surgery.'), (HYALOID_C, 'Posterior Hyaloid / Vitreous Face', 'Bright reflective line above the retina. ATTACHED = present (with or without VMT). ' 'DETACHED (dashed) = PVD present. Stage 3: partially detached. ' 'Stage 4: completely detached (Weiss ring clinically).'), (VMT_C, 'VMT Traction Lines', 'Visible in Stages 1a/1b/2. V-shaped attachment pulling the fovea anteriorly. ' 'Causes elevation of the fovea (flattened foveal pit or upward bulge). ' 'Treated with ocriplasmin or vitrectomy.'), (IS_OS_C, 'IS/OS Junction (Ellipsoid Zone)', 'Bright hyperreflective band at outer retina — represents mitochondria-rich ' 'inner segments of photoreceptors. Intact = good visual prognosis. ' 'Disrupted or absent = poor visual outcome even after anatomical closure.'), (RPE_C, 'RPE Band', 'Outermost bright hyperreflective band. Loss of RPE = poor prognosis. ' 'RPE changes under hole (drusen, atrophy) may indicate concurrent AMD. ' 'Smooth intact RPE = favourable for visual recovery.'), ] fx, fy = 0.6, 14.0 box_w = 13.8 for col, feat_title, feat_desc in features: rounded_box(ax_main, fx, fy - 0.1, 0.35, 0.9, fc=col, ec='none', alpha=0.9) label(ax_main, fx + 0.55, fy + 0.62, feat_title, fs=9.5, c=col, bold=True) # Wrap description manually into ~120-char lines words = feat_desc.split() lines, cur = [], '' for w in words: if len(cur) + len(w) + 1 < 115: cur = (cur + ' ' + w).strip() else: lines.append(cur) cur = w lines.append(cur) for i, ln in enumerate(lines[:2]): label(ax_main, fx + 0.55, fy + 0.32 - i*0.26, ln, fs=8.2, c=SUBTEXT) fy -= 1.55 # ══════════════════════════════════════════════════════════════════ # OCT MEASUREMENT DIAGRAM (bottom right) # ══════════════════════════════════════════════════════════════════ rounded_box(ax_main, 15.2, 1.5, 6.5, 13.7, fc=PANEL, ec=ACCENT4, lw=1.5) label(ax_main, 18.45, 14.85, 'IVTS SIZE MEASUREMENT', fs=10.5, c=ACCENT4, bold=True, align='center') label(ax_main, 18.45, 14.5, 'Measured at narrowest point on OCT', fs=8.5, c=SUBTEXT, align='center') # Draw a FTMH cross-section with measurement arrows mcx, mby = 18.45, 9.5 draw_oct(ax_main, mcx, mby, variant='stage3', scale=0.58) # Measurement arrow across narrowest point (at base) hw_m = 0.45 * 0.58 * 3.0 / 3.0 # approx meas_y = mby + 0.06 ax_main.annotate('', xy=(mcx + hw_m * 0.85, meas_y), xytext=(mcx - hw_m * 0.85, meas_y), arrowprops=dict(arrowstyle='<->', color=ACCENT4, lw=2, mutation_scale=10), zorder=9) label(ax_main, mcx, meas_y + 0.22, 'Minimum linear diameter (MLD)', fs=8.5, c=ACCENT4, bold=True, align='center') # IVTS size categories size_cats = [ ('<250 µm', 'Small FTMH', ACCENT2), ('250–400 µm', 'Medium FTMH', ACCENT3), ('>400 µm', 'Large FTMH', ACCENT5), ] sy = 8.85 for size, cat, col in size_cats: rounded_box(ax_main, 15.4, sy - 0.25, 6.1, 0.6, fc=col, ec='none', alpha=0.2) label(ax_main, 16.0, sy, size, fs=10, c=col, bold=True) label(ax_main, 18.1, sy, cat, fs=10, c=WHITE) sy -= 0.75 # PRognosis table label(ax_main, 18.45, 7.5, 'SURGICAL PROGNOSIS', fs=10, c=ACCENT2, bold=True, align='center') prog = [ ('Small (<250 µm)', 'Best', GREEN_A), ('Medium (250–400)', 'Good', ACCENT3), ('Large (>400 µm)', 'Guarded', ACCENT5), ('Duration >6 mo', 'Worse', ACCENT5), ('IS/OS intact', 'Better', GREEN_A), ('RPE intact', 'Better', GREEN_A), ] py2 = 7.05 for size, prog_txt, col in prog: label(ax_main, 15.5, py2, f'• {size}', fs=8.5, c=SUBTEXT) label(ax_main, 20.2, py2, prog_txt, fs=8.5, c=col, bold=True) py2 -= 0.46 # Key tests label(ax_main, 18.45, 4.5, 'BEDSIDE TESTS', fs=10, c=ACCENT1, bold=True, align='center') tests = [ ('Watzke-Allen', 'Beam THINNED / BROKEN = FTMH'), ('Amsler Grid', 'Central distortion (non-specific)'), ('FAF Stage 3/4','Hyperfluorescent foveolar spot'), ('FFA', 'Window defect (OCT preferred)'), ] ty = 4.05 for tname, finding in tests: label(ax_main, 15.5, ty, f'• {tname}:', fs=8.5, c=ACCENT3, bold=True) label(ax_main, 17.5, ty, finding, fs=8.2, c=SUBTEXT) ty -= 0.48 # ── Source footer ────────────────────────────────────────────── label(ax_main, 11, 0.45, 'Source: Kanski\'s Clinical Ophthalmology 10th Ed. pp. 611–629 · ' 'Wills Eye Manual pp. 867–869 · IVTS Group Classification', fs=8.5, c=SUBTEXT, align='center') plt.savefig('/tmp/workspace/ffa-pdf/OCT_Macular_Hole_Guide.png', dpi=150, bbox_inches='tight', facecolor=BG) plt.close() print("Done") PYEOF
python3 << 'PYEOF' import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt import matplotlib.patches as mpatches from matplotlib.patches import FancyBboxPatch import numpy as np fig = plt.figure(figsize=(22, 30), facecolor='#0A1628') ax = fig.add_axes([0, 0, 1, 1]) ax.set_xlim(0, 22); ax.set_ylim(0, 30); ax.axis('off') BG= '#0A1628'; PANEL='#112240'; PANEL2='#0D1B36' A1='#4FC3F7'; A2='#26A69A'; A3='#FFA726'; A4='#AB47BC'; A5='#EF5350' GRN='#66BB6A'; WHT='#ECEFF4'; SUB='#90A4AE' ILM_C='#80DEEA'; NFL_C='#4FC3F7'; IPL_C='#3F7FD4'; INL_C='#3949AB' OPL_C='#5E35B1'; ONL_C='#7B1FA2'; ELM_C='#AD1457'; ISO_C='#E91E63' OS_C='#F48FB1'; RPE_C='#BF360C'; CHO_C='#4E342E'; VIT_C='#1A3A5C' HYA_C='#29B6F6'; SRF_C='#4DD0E1'; CYS_C='#0097A7' OPR_C='#FFB300'; VMT_C='#F44336'; HOL_C='#020810' def rbox(ax,x,y,w,h,fc,ec=None,lw=1,alpha=1): p=FancyBboxPatch((x,y),w,h,boxstyle="round,pad=0.12", facecolor=fc,edgecolor=ec or fc,linewidth=lw,alpha=alpha,zorder=2) ax.add_patch(p) def lbl(ax,x,y,t,fs=9,c=WHT,bold=False,ha='left',va='center'): ax.text(x,y,t,fontsize=fs,color=c,fontweight='bold'if bold else 'normal', ha=ha,va=va,zorder=8) def draw_oct(ax, cx, by, variant='normal', scale=0.72): W = 3.0*scale xs = np.linspace(cx-W, cx+W, 300) xn = (xs-cx)/W # Fixed layer heights rpe_b=by; rpe_t=by+0.12 os_t=rpe_t+0.13; iso_t=os_t+0.04; elm_b=iso_t+0.02; elm_t=elm_b+0.03 onl_thick = 0.28 + 0.10*np.exp(-xn**2/0.18) onl_b=elm_t; onl_t_arr=onl_b+onl_thick opl_t=onl_t_arr+0.10; inl_t=opl_t+0.12; ipl_t=inl_t+0.14 nfl_thick=0.10*(0.3+0.7*xn**2) nfl_t=ipl_t+nfl_thick fov_dip=0.20*np.exp(-xn**2/0.09) ilm_s=nfl_t-fov_dip vit_top=by+2.1 hole_hw=0.0; schisis=False; srf=False; operculum=False hyaloid=None; hya_ls='-'; vmt_lines=False lamellar=False; resolved=False; weiss=False if variant=='vma': hyaloid=np.full_like(xs, vit_top-0.15) att=np.abs(xn)<0.15; hyaloid[att]=ilm_s[att]+0.01 elif variant=='vmt': fov_dip*=0.2; ilm_s=nfl_t-fov_dip hyaloid=np.full_like(xs,vit_top-0.05) hyaloid-=0.25*np.exp(-xn**2/0.12) vmt_lines=True; schisis=True elif variant=='stage1b': fov_dip*=0.1; ilm_s=nfl_t-fov_dip hyaloid=np.full_like(xs,vit_top-0.05) hyaloid-=0.22*np.exp(-xn**2/0.10) vmt_lines=True; schisis=True elif variant=='stage2': hole_hw=0.22; srf=True; operculum=True hyaloid=np.full_like(xs,vit_top-0.05) hyaloid-=0.15*np.exp(-xn**2/0.06) vmt_lines=True elif variant=='stage3': hole_hw=0.45; srf=True; operculum=True hyaloid=np.full_like(xs,vit_top-0.25); hya_ls='--' elif variant=='stage4': hole_hw=0.50; srf=True; operculum=True hyaloid=np.full_like(xs,vit_top-0.05); weiss=True elif variant=='lamellar': lamellar=True hyaloid=np.full_like(xs,vit_top-0.15) elif variant=='resolved': resolved=True hyaloid=np.full_like(xs,vit_top-0.15) m=np.abs(xn)>hole_hw if hole_hw>0 else np.ones(len(xs),bool) # choroid ax.fill_between(xs,rpe_b-0.55,rpe_b,color=CHO_C,alpha=0.75,zorder=2) # RPE ax.fill_between(xs,rpe_b,rpe_t,color=RPE_C,alpha=0.92,zorder=3) # outer retina where not hole ax.fill_between(xs,rpe_t,os_t,color=OS_C,alpha=0.7,zorder=3,where=m) ax.fill_between(xs,os_t,iso_t,color=ISO_C,alpha=0.9,zorder=4,where=m) ax.fill_between(xs,elm_b,elm_t,color=ELM_C,alpha=0.85,zorder=4,where=m) ax.fill_between(xs,onl_b,onl_t_arr,color=ONL_C,alpha=0.75,zorder=3,where=m) ax.fill_between(xs,onl_t_arr,opl_t,color=OPL_C,alpha=0.75,zorder=3,where=m) if not lamellar: ax.fill_between(xs,opl_t,inl_t,color=INL_C,alpha=0.75,zorder=3,where=m) ax.fill_between(xs,inl_t,ipl_t,color=IPL_C,alpha=0.80,zorder=3,where=m) ax.fill_between(xs,ipl_t,nfl_t,color=NFL_C,alpha=0.80,zorder=3,where=m) ax.plot(xs,ilm_s,color=ILM_C,lw=2,zorder=5) else: scoop=0.20*np.exp(-xn**2/0.10) ax.fill_between(xs,onl_b,onl_b+scoop,color=BG,alpha=1,zorder=4) ax.fill_between(xs,opl_t,inl_t,color=INL_C,alpha=0.75,zorder=3) ax.fill_between(xs,inl_t,ipl_t,color=IPL_C,alpha=0.80,zorder=3) ax.fill_between(xs,ipl_t,nfl_t,color=NFL_C,alpha=0.80,zorder=3) ax.plot(xs,ilm_s,color=ILM_C,lw=2,zorder=5) # hole gap if hole_hw>0: hm=~m ax.fill_between(xs,rpe_t,np.full_like(xs,by+1.6),color=HOL_C,alpha=1,zorder=5,where=hm) # SRF if srf: sm=np.abs(xn)<hole_hw*1.5 ax.fill_between(xs,rpe_t,rpe_t+0.12,color=SRF_C,alpha=0.55,zorder=4,where=sm) # schisis/cysts if schisis: cm=np.abs(xn)<0.28 ch=0.13*np.exp(-xn**2/0.05) ax.fill_between(xs,onl_t_arr-ch/2,onl_t_arr+ch/2,color=CYS_C,alpha=0.7,zorder=5,where=cm) # operculum if operculum: opy=by+1.4+hole_hw*0.3 opx=np.linspace(cx-0.35-hole_hw*0.25,cx+0.35+hole_hw*0.25,60) opy_a=np.full_like(opx,opy)+0.04*np.sin(np.pi*np.linspace(0,1,60)) ax.fill_between(opx,opy_a-0.045,opy_a+0.045,color=OPR_C,alpha=0.85,zorder=6) ax.plot(opx,opy_a+0.045,color='#FFD54F',lw=1.2,zorder=7) # hyaloid if hyaloid is not None: ax.plot(xs,hyaloid,color=HYA_C,lw=2.2,linestyle=hya_ls,zorder=6,alpha=0.9) # VMT arrows if vmt_lines and hyaloid is not None: for xoff in [-0.12,0.0,0.12]: xi=int((xoff/(2*W)+0.5)*299); xi=max(0,min(299,xi)) ax.annotate('',xy=(xs[xi],ilm_s[xi]),xytext=(xs[xi],hyaloid[xi]), arrowprops=dict(arrowstyle='->',color=VMT_C,lw=1.6,mutation_scale=7),zorder=7) # resolved defect if resolved: dm=np.abs(xn)<0.06 ax.fill_between(xs,iso_t-0.01,iso_t+0.05,color=BG,alpha=1,zorder=6,where=dm) ax.plot([cx-0.18,cx+0.18],[iso_t+0.01,iso_t+0.01], color=ISO_C,lw=1.5,linestyle=':',zorder=7) # weiss ring if weiss: wr=mpatches.Ellipse((cx,hyaloid[150]+0.25),0.75,0.16, facecolor='none',edgecolor=HYA_C,lw=2,linestyle='--',zorder=7) ax.add_patch(wr) # vitreous fill ax.fill_between(xs,ilm_s,np.full_like(xs,by+2.1),color=VIT_C,alpha=0.18,zorder=1) return ilm_s, rpe_t, iso_t, onl_t_arr, ipl_t # ══════════════════════════════════════════════════════════════════ # TITLE rbox(ax,0.3,28.4,21.4,1.35,PANEL,A1,2) lbl(ax,11,29.22,'OCT INTERPRETATION GUIDE — MACULAR HOLE',fs=20,c=WHT,bold=True,ha='center') lbl(ax,11,28.72,'ILM · Retinal Layers · Cysts · Operculum · SRF · VMT | Kanski 10th Ed.',fs=11,c=A1,ha='center') # ── LAYER LEGEND (right column) ────────────────────────────────── rbox(ax,15.3,22.2,6.4,5.9,PANEL,A2,1.5) lbl(ax,18.5,27.82,'RETINAL LAYERS ON OCT',fs=11,c=A2,bold=True,ha='center') lbl(ax,18.5,27.46,'(inner → outer)',fs=8.5,c=SUB,ha='center') layers=[ (ILM_C,'ILM','Internal Limiting Membrane — thin bright line'), (NFL_C,'NFL','Nerve Fibre Layer (hyperreflective)'), (IPL_C,'IPL','Inner Plexiform Layer'), (INL_C,'INL','Inner Nuclear Layer'), (OPL_C,'OPL','Outer Plexiform Layer (Henle fibres)'), (ONL_C,'ONL','Outer Nuclear Layer (thickest at fovea)'), (ELM_C,'ELM','External Limiting Membrane'), (ISO_C,'IS/OS','Ellipsoid zone — bright outer band (KEY prognostic)'), (OS_C, 'OS', 'Outer Segments of photoreceptors'), (RPE_C,'RPE','RPE — outermost bright hyperreflective band'), (CHO_C,'Choroid','Choroid (hyporeflective, vessels)'), ] ly=27.05 for col,ab,desc in layers: rbox(ax,15.5,ly-0.19,0.5,0.35,col,'none',0,0.9) lbl(ax,16.15,ly,ab,fs=8,c=col,bold=True) lbl(ax,16.9,ly,desc,fs=7.8,c=SUB) ly-=0.43 ly-=0.1 spec=[(HYA_C,'Posterior hyaloid face'),(VMT_C,'VMT traction lines'), (CYS_C,'Intraretinal cyst / schisis'),(SRF_C,'Subretinal fluid (SRF)'), (OPR_C,'Operculum (avulsed ILM/retina)')] for col,desc in spec: rbox(ax,15.5,ly-0.19,0.5,0.35,col,'none',0,0.9) lbl(ax,16.15,ly,desc,fs=7.8,c=col) ly-=0.40 # ── SECTION HEADERS ────────────────────────────────────────────── rbox(ax,0.3,27.82,14.7,0.50,'#0D3358',A1,1.2) lbl(ax,7.65,28.07,'PRECURSOR STAGES (No FTMH)',fs=11,c=A1,bold=True,ha='center') rbox(ax,0.3,23.08,14.7,0.50,'#2C1654',A4,1.2) lbl(ax,7.65,23.33,'FULL-THICKNESS MACULAR HOLE (FTMH)',fs=11,c=A4,bold=True,ha='center') rbox(ax,0.3,18.38,14.7,0.50,'#1A3D1A',GRN,1.2) lbl(ax,7.65,18.63,'DIFFERENTIALS & POST-TREATMENT',fs=11,c=GRN,bold=True,ha='center') # ── PANELS CONFIG ───────────────────────────────────────────────── panels=[ # (cx, base_y, variant, title, subtitle, [(label,lx,ly,color)]) (2.8, 25.2,'normal','NORMAL FOVEA','Reference baseline', [(ILM_C,'ILM',2.18,26.98),(NFL_C,'NFL',2.18,26.65), (ISO_C,'IS/OS (Ellipsoid)',2.18,25.77),(RPE_C,'RPE',2.18,25.21), (A1,'Foveal pit',2.18,26.35)]), (7.5, 25.2,'vma','VMA','Attachment, no distortion', [(HYA_C,'Posterior hyaloid',6.88,27.05),(GRN,'Focal attachment',6.88,26.60), (ISO_C,'IS/OS intact',6.88,25.77)]), (12.2,25.2,'vmt','VMT','Traction + foveal elevation', [(VMT_C,'VMT traction',11.58,26.88),(CYS_C,'Schisis cavity',11.58,26.35), (A3,'Elevated fovea',11.58,26.08)]), (2.8, 20.5,'stage1b','STAGE 1b','Foveal detachment + cyst', [(VMT_C,'VMT traction',2.18,22.28),(CYS_C,'Intraretinal cyst',2.18,21.52), (A1,'ILM intact',2.18,22.68)]), (7.5, 20.5,'stage2','STAGE 2 (<400 µm)','Small FTMH + operculum + VMT', [(OPR_C,'Operculum',6.88,22.15),(VMT_C,'VMT present',6.88,22.55), (SRF_C,'SRF',6.88,20.65),(HOL_C,'FTMH',7.5,21.28)]), (12.2,20.5,'stage3','STAGE 3 (≥400 µm)','Large FTMH, no PVD', [(HYA_C,'Hyaloid (dashed)',11.58,22.38),(OPR_C,'Free operculum',11.58,21.88), (SRF_C,'SRF cuff',11.58,20.7)]), (2.8, 15.8,'stage4','STAGE 4 + PVD','Complete PVD + Weiss ring', [(HYA_C,'Weiss ring (PVD)',2.18,17.88),(OPR_C,'Operculum free',2.18,16.98), (SRF_C,'Large SRF',2.18,15.95)]), (7.5, 15.8,'lamellar','LAMELLAR HOLE','Partial thickness — NOT FTMH', [(ILM_C,'ILM INTACT (key)',6.88,17.65),(ONL_C,'Outer retina scooped',6.88,16.38), (GRN,'Inner retina intact',6.88,17.15)]), (12.2,15.8,'resolved','RESOLVED / MICROHOLE','Closed hole — IS/OS defect remains', [(ILM_C,'ILM intact',11.58,17.65),(ISO_C,'IS/OS defect (dotted)',11.58,16.1), (GRN,'Foveal contour restored',11.58,16.75)]), ] for (cx,by,variant,title,sub,annots) in panels: pw,ph=4.6,4.05 rbox(ax,cx-pw/2+0.05,by-0.3,pw-0.1,ph,PANEL2,A2,0.8,0.9) draw_oct(ax,cx,by,variant=variant,scale=0.72) lbl(ax,cx,by+3.55,title,fs=10.5,c=WHT,bold=True,ha='center') lbl(ax,cx,by+3.2,sub,fs=8.5,c=SUB,ha='center') for (col,txt,lx,ly_a) in annots: lbl(ax,lx,ly_a,txt,fs=7.5,c=col,ha='left') # ══════════════════════════════════════════════════════════════════ # BOTTOM: OCT FEATURES GUIDE rbox(ax,0.3,1.5,14.6,13.7,PANEL,A3,1.5) lbl(ax,7.65,14.88,'DETAILED OCT FEATURES TO IDENTIFY',fs=12,c=A3,bold=True,ha='center') lbl(ax,7.65,14.48,'Key structures on every macular hole OCT scan',fs=9,c=SUB,ha='center') feats=[ (ILM_C,'ILM (Internal Limiting Membrane)', 'Thin bright cyan line at inner retinal surface. Intact = pseudohole/lamellar (good sign).', 'Peeled during vitrectomy to improve hole closure rates.'), (CYS_C,'Intraretinal Cysts / Schisis', 'Hyporeflective (dark) spaces in ONL / between retinal layers. Earliest sign of hole formation.', 'Distinguish from CME: MH cysts are foveal, round, no disc/perifoveal leakage.'), (OPR_C,'Operculum', 'Small oval hyperreflective fragment above hole — avulsed ILM ± NFL. Indicates true FTMH.', '"Pseudo-operculum" = still attached to posterior hyaloid. Free operculum = Stages 3/4.'), (SRF_C,'Subretinal Fluid (SRF) Cuff', 'Thin hyporeflective crescent at hole base between neurosensory retina and RPE.', 'Stages 3 & 4. Width correlates with hole duration. Resolves after successful surgery.'), (HYA_C,'Posterior Hyaloid / Vitreous Face', 'Bright reflective line above retina. Solid line = attached. Dashed = detached (PVD).', 'Stage 2 = attached with VMT. Stage 3 = partially detached. Stage 4 = completely detached.'), (VMT_C,'VMT Traction Lines', 'V-shaped red lines pulling ILM anteriorly. Elevated fovea = flattened/reversed pit.', 'Target of ocriplasmin (pharmacological vitreolysis) for Stages 1–2.'), (ISO_C,'IS/OS (Ellipsoid Zone)', 'Bright band at outer retina = photoreceptor inner segments. KEY prognostic marker.', 'Intact = good visual recovery. Disrupted = poor VA even after anatomical closure.'), (RPE_C,'RPE Band', 'Outermost bright band. RPE integrity determines long-term visual recovery.', 'RPE changes (drusen, atrophy) indicate concurrent AMD — affects surgical planning.'), ] fx,fy=0.55,14.05 for (col,ftitle,desc1,desc2) in feats: rbox(ax,fx,fy-0.12,0.38,0.88,col,'none',0,0.9) lbl(ax,fx+0.55,fy+0.58,ftitle,fs=9.5,c=col,bold=True) lbl(ax,fx+0.55,fy+0.28,desc1,fs=8.2,c=WHT) lbl(ax,fx+0.55,fy-0.02,desc2,fs=7.8,c=SUB) fy-=1.52 # ── Measurement + prognosis (right bottom) ─────────────────────── rbox(ax,15.3,1.5,6.4,13.7,PANEL,A4,1.5) lbl(ax,18.5,14.88,'IVTS MEASUREMENT',fs=11,c=A4,bold=True,ha='center') lbl(ax,18.5,14.48,'Narrowest point (MLD) on OCT',fs=8.5,c=SUB,ha='center') draw_oct(ax,18.5,9.5,variant='stage3',scale=0.58) hw=0.45*0.58*1.0 ax.annotate('',xy=(18.5+hw*0.85,9.56),xytext=(18.5-hw*0.85,9.56), arrowprops=dict(arrowstyle='<->',color=A4,lw=2,mutation_scale=10),zorder=9) lbl(ax,18.5,9.82,'← MLD →',fs=9,c=A4,bold=True,ha='center') cats=[('<250 µm','Small FTMH',A2),('250–400 µm','Medium FTMH',A3),('>400 µm','Large FTMH',A5)] sy=8.88 for sz,cat,col in cats: rbox(ax,15.5,sy-0.22,6.0,0.55,col,'none',0,0.2) lbl(ax,16.0,sy,sz,fs=10,c=col,bold=True); lbl(ax,18.2,sy,cat,fs=10,c=WHT) sy-=0.72 lbl(ax,18.5,7.45,'SURGICAL PROGNOSIS',fs=10,c=A2,bold=True,ha='center') prog=[('Small <250 µm','Best',GRN),('Medium 250–400','Good',A3), ('Large >400 µm','Guarded',A5),('Duration >6 mo','Worse',A5), ('IS/OS intact','Better',GRN),('RPE intact','Better',GRN)] py2=7.02 for sz,pg,col in prog: lbl(ax,15.55,py2,f'• {sz}',fs=8.5,c=SUB) lbl(ax,20.2,py2,pg,fs=8.5,c=col,bold=True); py2-=0.45 lbl(ax,18.5,4.65,'BEDSIDE TESTS',fs=10,c=A1,bold=True,ha='center') tests=[('Watzke-Allen','Beam thinned / BROKEN = FTMH'), ('Amsler Grid','Central distortion (non-specific)'), ('FAF Stages 3/4','Hyperfluorescent foveolar spot'), ('FFA','Window defect (use OCT instead)')] ty=4.22 for tn,fi in tests: lbl(ax,15.55,ty,f'• {tn}:',fs=8.5,c=A3,bold=True) lbl(ax,17.55,ty,fi,fs=8.2,c=SUB); ty-=0.48 lbl(ax,11,0.45, "Source: Kanski's Clinical Ophthalmology 10th Ed. pp. 611–629 · Wills Eye Manual pp. 867–869 · IVTS Classification", fs=8.5,c=SUB,ha='center') plt.savefig('/tmp/workspace/ffa-pdf/OCT_Macular_Hole_Guide.png', dpi=150,bbox_inches='tight',facecolor=BG) plt.close() print("Done") PYEOF
python3 << 'PYEOF' from reportlab.lib.pagesizes import A4 from reportlab.lib import colors from reportlab.lib.styles import ParagraphStyle from reportlab.lib.units import cm, mm from reportlab.platypus import (SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, HRFlowable) from reportlab.platypus import Image as RLImage from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY OUTPUT = "/tmp/workspace/ffa-pdf/OCT_Macular_Hole_Visual_Guide.pdf" W, H = A4 doc = SimpleDocTemplate(OUTPUT, pagesize=A4, leftMargin=1.5*cm, rightMargin=1.5*cm, topMargin=1.5*cm, bottomMargin=1.5*cm, title="OCT Interpretation Guide — Macular Hole") CW = W - 3.0*cm DARK=colors.HexColor("#0A1628"); NAVY=colors.HexColor("#1A3A5C") BLUE=colors.HexColor("#2E6DA4"); TEAL=colors.HexColor("#26A69A") AMB=colors.HexColor("#FFA726"); RED=colors.HexColor("#C0392B") GRN=colors.HexColor("#1E8449"); PUR=colors.HexColor("#7D3C98") LBLUE=colors.HexColor("#D6E8F7"); WHITE=colors.white GREY=colors.HexColor("#566573"); GREY_L=colors.HexColor("#F2F3F4") ILM=colors.HexColor("#80DEEA"); ISO=colors.HexColor("#E91E63") RPE=colors.HexColor("#BF360C"); SRF=colors.HexColor("#4DD0E1") OPR=colors.HexColor("#FFB300"); VMT=colors.HexColor("#F44336") def S(n,**k): d=dict(fontName='Helvetica',fontSize=10,leading=14,textColor=colors.HexColor("#1C2833"),spaceAfter=2) d.update(k); return ParagraphStyle(n,**d) ts=S('t',fontName='Helvetica-Bold',fontSize=18,textColor=WHITE,alignment=TA_CENTER,leading=24) ss=S('s',fontSize=10,textColor=colors.HexColor("#AED6F1"),alignment=TA_CENTER) h1=S('h1',fontName='Helvetica-Bold',fontSize=12,textColor=WHITE,leading=16,leftIndent=6) h2=S('h2',fontName='Helvetica-Bold',fontSize=10.5,textColor=NAVY,spaceBefore=4,spaceAfter=2) bd=S('b',fontSize=9.5,leading=14,alignment=TA_JUSTIFY) bt=S('bt',fontSize=9.5,leading=14,leftIndent=12) sm=S('sm',fontSize=8,textColor=GREY,alignment=TA_CENTER,leading=11) ch=S('ch',fontName='Helvetica-Bold',fontSize=9.5,textColor=WHITE,alignment=TA_CENTER,leading=13) cb=S('cb',fontName='Helvetica-Bold',fontSize=9,textColor=NAVY,leading=12) cn=S('cn',fontSize=9,leading=12) tip=S('ti',fontName='Helvetica-Oblique',fontSize=9.5,textColor=TEAL,leftIndent=8,leading=13) def hdr(txt,bg=BLUE): t=Table([[Paragraph(txt,h1)]],colWidths=[CW]) t.setStyle(TableStyle([('BACKGROUND',(0,0),(-1,-1),bg), ('TOPPADDING',(0,0),(-1,-1),7),('BOTTOMPADDING',(0,0),(-1,-1),7), ('LEFTPADDING',(0,0),(-1,-1),10),('BOX',(0,0),(-1,-1),0.6,DARK)])) return t def gtable(data,cols,hbg=BLUE,a1=LBLUE,a2=WHITE): fmt=[] for ri,row in enumerate(data): frow=[] for ci,cell in enumerate(row): if ri==0: frow.append(Paragraph(str(cell),ch)) elif ci==0: frow.append(Paragraph(str(cell),cb)) else: frow.append(Paragraph(str(cell),cn)) fmt.append(frow) t=Table(fmt,colWidths=cols,repeatRows=1) ts=[('BACKGROUND',(0,0),(-1,0),hbg), ('GRID',(0,0),(-1,-1),0.4,colors.HexColor("#B0C4DE")), ('BOX',(0,0),(-1,-1),0.8,hbg), ('VALIGN',(0,0),(-1,-1),'MIDDLE'), ('TOPPADDING',(0,0),(-1,-1),5),('BOTTOMPADDING',(0,0),(-1,-1),5), ('LEFTPADDING',(0,0),(-1,-1),5)] for i in range(1,len(data)): ts.append(('BACKGROUND',(0,i),(-1,i),a1 if i%2==1 else a2)) t.setStyle(TableStyle(ts)); return t def ibox(txt,bg=colors.HexColor("#FFF3E0"),bc=AMB): t=Table([[Paragraph(txt,tip)]],colWidths=[CW]) t.setStyle(TableStyle([('BACKGROUND',(0,0),(-1,-1),bg), ('BOX',(0,0),(-1,-1),1,bc), ('TOPPADDING',(0,0),(-1,-1),6),('BOTTOMPADDING',(0,0),(-1,-1),6), ('LEFTPADDING',(0,0),(-1,-1),10)])); return t story=[] # Title tb=Table([[Paragraph('OCT INTERPRETATION GUIDE',ts)], [Paragraph('Macular Hole — ILM · Cysts · Operculum · SRF · VMT',ss)], [Paragraph('Kanski 10th Ed. pp. 611–629 · Wills Eye Manual · IVTS Classification',sm)]],colWidths=[CW]) tb.setStyle(TableStyle([('BACKGROUND',(0,0),(-1,-1),DARK), ('TOPPADDING',(0,0),(-1,-1),8),('BOTTOMPADDING',(0,0),(-1,-1),8), ('LEFTPADDING',(0,0),(-1,-1),10),('BOX',(0,0),(-1,-1),1.5,BLUE)])) story.append(tb); story.append(Spacer(1,4*mm)) # Main diagram story.append(hdr('1. VISUAL OCT DIAGRAM — ALL STAGES SIDE BY SIDE',bg=PUR)) story.append(Spacer(1,3*mm)) try: img=RLImage('/tmp/workspace/ffa-pdf/OCT_Macular_Hole_Guide.png', width=CW, height=CW*(30/22)) story.append(img) story.append(Paragraph( 'Fig: Colour-coded OCT cross-sections for each stage. ' 'ILM=cyan line, IS/OS=pink band, RPE=red-brown base, SRF=teal, ' 'operculum=gold, VMT=red arrows, posterior hyaloid=blue line.',sm)) except Exception as e: story.append(Paragraph(f'[Image error: {e}]',bd)) story.append(Spacer(1,5*mm)) # Key OCT features table story.append(hdr('2. KEY OCT STRUCTURES — WHAT TO IDENTIFY',bg=colors.HexColor("#1A5276"))) story.append(Spacer(1,3*mm)) oct_feats=[ ['Structure','Appearance on OCT','Clinical Significance'], ['ILM\n(Internal Limiting Membrane)', 'Thin hyperreflective (bright) line at inner retinal surface', 'Intact = pseudohole/lamellar. Absent = true hole or post-surgery. Peeled in vitrectomy.'], ['IS/OS (Ellipsoid Zone)', 'Bright hyperreflective band in outer retina (2nd from RPE)', 'KEY prognostic marker. Intact = good VA recovery. Disrupted = poor prognosis.'], ['RPE', 'Outermost bright hyperreflective band, just above choroid', 'Intact RPE = better recovery. Concurrent AMD changes affect surgical planning.'], ['Intraretinal Cysts', 'Hyporeflective (dark) round spaces in ONL/inner retina', 'Earliest sign of hole formation (Stages 1a/1b). Schisis cavity at fovea.'], ['Operculum', 'Small oval hyperreflective fragment floating above hole', 'Avulsed ILM ± NFL. Pseudo-operculum = still attached to hyaloid face. Free = Stages 3/4.'], ['SRF (Subretinal Fluid)', 'Thin hyporeflective crescent between neurosensory retina and RPE', 'Stages 3 & 4 only. Width ∝ hole size/duration. Resolves after surgery.'], ['Posterior Hyaloid', 'Bright reflective line above retina. Solid = attached. Dashed = detached.', 'Stage 2 = attached (VMT). Stage 3 = partial PVD. Stage 4 = complete PVD + Weiss ring.'], ['VMT Traction', 'V-shaped hyaloid pulling ILM anteriorly. Foveal elevation on OCT.', 'Stages 1a/1b/2. Target of ocriplasmin. Causes anomalous PVD → hole formation.'], ['Resolved/Microhole', 'Intact foveal contour but tiny IS/OS junction break (dotted)', 'Diagnostic of spontaneously healed/surgically closed microhole. VA may be near-normal.'], ] story.append(gtable(oct_feats,[3.2*cm,4.8*cm,CW-8.0*cm],hbg=NAVY,a1=LBLUE)) story.append(Spacer(1,5*mm)) # Stage-by-stage OCT features story.append(hdr('3. STAGE-BY-STAGE OCT FEATURES',bg=TEAL)) story.append(Spacer(1,3*mm)) stages=[ ['Stage','OCT Key Findings','ILM?','IS/OS?','Hyaloid?','SRF?'], ['Normal','Normal foveal pit · All layers intact · Regular contour','Intact','Intact','Attached','No'], ['VMA','Vitreous attached at fovea ≤3 mm · No distortion · No cysts','Intact','Intact','Attached (focal)','No'], ['VMT','Foveal elevation/distortion · V-traction · Schisis cavity · No FTMH','Intact','Intact','Attached (traction)','No'], ['Stage 1b\n(Occult)','Foveal detachment · Intraretinal cyst/schisis · VMT present · No FTMH','Intact','Intact','Attached (VMT)','No'], ['Stage 2\n(Early FTMH)','Small FTMH <400 µm · Operculum · VMT still present · Cystic edges','Breached','Usually intact','Attached (VMT)','Small'], ['Stage 3\n(FTMH no PVD)','Large FTMH ≥400 µm · Free operculum · Cuff of SRF · No PVD','Absent at hole','May be disrupted','Partially detached','Present'], ['Stage 4\n(FTMH + PVD)','Large FTMH ≥400 µm · Free operculum · Large SRF · Complete PVD','Absent at hole','May be disrupted','Completely detached\n(Weiss ring)','Present'], ['Lamellar Hole','Partial thickness scooping of outer retina · Inner retina INTACT · ILM present','INTACT','Intact','Variable','No'], ['Resolved/\nMicrohole','Restored foveal contour · Tiny IS/OS defect (dotted line) · ILM present','Present','Micro-defect','Detached (PVD)','No'], ] story.append(gtable(stages,[2.0*cm,5.0*cm,1.5*cm,1.5*cm,2.5*cm,CW-12.5*cm],hbg=TEAL, a1=colors.HexColor("#D0F0EB"))) story.append(Spacer(1,4*mm)) story.append(ibox( 'TIP: OCT is the INVESTIGATION OF CHOICE for diagnosis and staging. ' 'FFA is NOT required. Key question: Is ILM intact? Is IS/OS intact? ' 'Is hyaloid attached or detached? Is there SRF?', bg=colors.HexColor("#E8F8F5"),bc=TEAL)) story.append(Spacer(1,5*mm)) # Differential OCT features story.append(hdr('4. OCT DIFFERENTIAL DIAGNOSIS',bg=RED)) story.append(Spacer(1,3*mm)) dd=[ ['Condition','ILM','IS/OS','Retina Depth','SRF','Key OCT Clue'], ['True FTMH','Breached/absent','Disrupted (late)','Full-thickness gap','Yes (Stages 3/4)','Full-thickness gap from ILM to RPE'], ['Lamellar Hole','INTACT','Intact','Partial scooping of outer layers','No','Inner retina intact — outer retina scooped'], ['Pseudohole (ERM)','Intact','Intact','No tissue loss','No','ERM on surface · ILM preserved · Watzke -ve'], ['CME','Intact','Intact','Intraretinal cysts','No','Perifoveal/disc leakage · Multiple cysts · Cause present'], ['CSCR','Intact','Intact','Neurosensory detachment','Yes (sub)','Subretinal fluid pool · No hole · RPE bump'], ] story.append(gtable(dd,[2.5*cm,1.5*cm,1.5*cm,2.8*cm,1.5*cm,CW-9.8*cm], hbg=RED,a1=colors.HexColor("#FDEDEC"))) story.append(Spacer(1,5*mm)) # Treatment impact on OCT story.append(hdr('5. POST-TREATMENT OCT CHANGES',bg=GRN)) story.append(Spacer(1,3*mm)) post=[ ['Timing','Expected OCT Finding'], ['Day 1–7 post-op','Gas bubble visible (hyperreflective arc). Hole closure often evident early.'], ['Week 2–4','Hole edges approximating · Cystic spaces resolving · SRF disappearing'], ['Month 1–3','Foveal contour partially restored · IS/OS defect may still be visible'], ['Month 3–12','IS/OS junction progressively improving · Correlates with visual recovery'], ['Long-term residual','Micro-IS/OS defect · Mild foveal contour irregularity · Still diagnostic'], ['Failure','Persistent full-thickness gap · Cystic edges · ERM formation'], ] story.append(gtable(post,[3.0*cm,CW-3.0*cm],hbg=GRN,a1=colors.HexColor("#E9F7EF"))) story.append(Spacer(1,5*mm)) # Footer story.append(HRFlowable(width='100%',thickness=0.6,color=GREY,spaceAfter=3,spaceBefore=3)) story.append(Paragraph( "Reference: Kanski's Clinical Ophthalmology 10th Ed. pp. 611–629 · " "Wills Eye Manual pp. 867–869 · IVTS Group Classification", sm)) doc.build(story) print("PDF:", OUTPUT) PYEOF
ls -lh /tmp/workspace/ffa-pdf/
OCT Macular Hole Guide
Image · PNG
OCT Macular Hole Visual Guide
PDF Document · PDF
| Section | Content |
|---|---|
| 1. Visual Diagram | Full embedded 9-panel OCT diagram |
| 2. Key OCT Structures | 9-row table: ILM, IS/OS, RPE, cysts, operculum, SRF, hyaloid, VMT, resolved defect |
| 3. Stage-by-Stage OCT | 9-stage table with ILM/IS-OS/hyaloid/SRF status per stage |
| 4. OCT Differential Diagnosis | True FTMH vs Lamellar vs Pseudohole vs CME vs CSCR |
| 5. Post-Treatment OCT | Expected OCT changes from Day 1 to long-term |
Find Images nd explain
macular hole OCT optical coherence tomography stages

A comparative medical series illustrating the progression and spontaneous resolution of a macular hole (MH) following surgical intervention. The figure presents three stages (A, B, C), each containing a color fundus photograph (left) and a corresponding spectral-domain optical coherence tomography (OCT) cross-section (right).

This composite diagnostic image features four Spectral Domain Optical Coherence Tomography (SD-OCT) scans of the human retina, illustrating different stages and outcomes of macular hole (MH) surgery. The top panels show preoperative scans of two different cases (Case 21, left; Case 18, right) with active full-thickness macular holes. These images are annotated with measurements of hole morphology, including basal diameter (e.g., 2627 m vs. 1053 m), minimum top diameter, and height. Yellow outlines highlight intraretinal pseudocysts, with their respective areas calculated (e.g., 0.73 m). The bottom panels demonstrate postoperative outcomes: the bottom-left scan shows anatomical failure with a persistently open macular hole and significant cystic changes seven months post-surgery, while the bottom-right scan shows anatomical success with a closed macular hole and restoration of foveal contour four months post-surgery. These images serve as educational examples of how preoperative macular hole dimensions and the area of perifoveal pseudocysts may correlate with surgical prognosis and anatomical closure.

Summary : This figure presents a series of retinal fundus photographs and corresponding optical coherence tomography (OCT) scans, illustrating progressive stages of macular pathology.

This diagnostic image provides a series of cross-sectional B-scans obtained through Optical Coherence Tomography (OCT) of the human macula, demonstrating the segmentation of vitreoretinal pathology. The panel displays two separate clinical cases of macular holes (MH) across three stages of automated analysis: (a, d) original scans, (b, e) box selection modality, and (c, f) final segmented masks.

This diagnostic image series displays vertical Spectral-Domain Optical Coherence Tomography (SD-OCT) scans and corresponding infrared fundus images tracking the repair of a large macular hole (MH) using an anterior lens capsular flap transplant. The series is divided into four chronological stages: (a) Baseline, showing a full-thickness macular hole with distinct retinal layer disruption and subretinal fluid; (b) One month postoperatively, demonstrating initial closure with the hyperreflective capsular flap bridging the defect; (c) Six months postoperatively, showing further tissue integration and thickening at the foveal site; and (d) 12 months postoperatively, illustrating anatomical closure and foveal contour reorganization. The scans highlight the gradual restoration of retinal architecture, although some irregularities in the outer retinal layers persist. This sequence serves as an educational tool for ophthalmology residents to understand surgical outcomes and tissue remodeling in advanced macular hole management using autologous transplantation techniques. The imaging provides evidence of the flap acting as a scaffold for cellular migration and structural bridging.

This composite diagnostic image features four Spectral-Domain Optical Coherence Tomography (SD-OCT) cross-sections of the human retina, illustrating the progression of macular holes (MH) graded according to the Gass classification (Stages I through IV). Panels A–D demonstrate the morphological evolution of the foveal architecture. Stage I (Panel A) shows an early foveal detachment with inner retinal disruption. Stages II and III (Panels B and C) exhibit progressively enlarging full-thickness retinal defects with associated intraretinal cystic changes and vitreomacular traction. Stage IV (Panel D) depicts a complete, large-diameter full-thickness macular hole with total vitreous separation. Annotated in each panel are measurement indicators: a yellow line representing the 'minimum MH size' (the narrowest point of the defect) and a red line representing the 'basal MH size' (the extent of the hole at the level of the retinal pigment epithelium). The series highlights key clinical biomarkers including retinal layer separation, cystic edema at the hole edges, and the widening of the hole diameter as the condition advances, which are critical for surgical planning and prognostic assessment in ophthalmology.

This composite diagnostic image displays multiple infrared fundus photographs and corresponding spectral-domain optical coherence tomography (SD-OCT) scans of patients with North Carolina Macular Dystrophy (NCMD), specifically illustrating Grade 3 'macular caldera' lesions. Panel (a) shows significant choroidal atrophy at the lesion edge with relative preservation of the overlying neurosensory retina and retinal pigment epithelium (RPE), marked by an arrowhead. Panel (b) illustrates localized RPE atrophy at the lesion periphery where the choroid remains largely intact (arrow). Panel (c) provides a wide-field OCT view of a deep macular excavation, highlighting the thinning and potential loss of the RPE and outer retinal layers, though some neurosensory tissue remains as a thin covering over the lesion floor. Panel (d) demonstrates a significant structural complication: a full-thickness macular hole (arrow) overlying a deep coloboma-like excavated area. These scans highlight the phenotypic variability of NCMD, emphasizing the differential atrophy patterns of the retinal layers, RPE, and choroid in advanced stages of the disease.

This diagnostic composite illustrates the longitudinal progression from myopic macular retinoschisis (MRS) to early retinal detachment (RD) across three patient cases. The top row features posterior fundus photographs showing myopic changes and tessellated fundi. Below, four rows of Optical Coherence Tomography (OCT) cross-sections track four evolutionary stages. Initial examinations show MRS with clear splitting of retinal layers. Stage 1 demonstrates focal thickening and irregularity of the external retinal layer (marked by arrows). Stage 2 reveals the development of an outer lamellar hole (arrowheads) beneath the thickened area. Stage 3 shows horizontal separation and bridging of the retinoschisis layer (asterisks) as the outer lamellar hole expands. Stage 4 depicts advanced RD with the resolution of retinoschisis, where remnants of the external retina (open arrowheads) remain attached to the inner retinal layers. This image serves as a clinical reference for monitoring high myopia complications and the specific morphological changes preceding full-thickness macular holes or total foveal detachment.
macular hole fundus photo operculum SRF vitreomacular traction
Find Images nd explain
macular hole lamellar hole pseudohole OCT comparison
vitreomacular traction OCT epiretinal membrane macular hole surgery post-op

Key teaching point: The red and yellow measurement lines show MLD (narrowest point, red) vs basal diameter (at RPE level, yellow). IVTS classification uses MLD for size categorisation.

Key teaching point: ~10% of full-thickness holes close spontaneously. Resolved holes may still have a micro-IS/OS defect on OCT and variable visual recovery depending on how long the hole was open.

Key teaching point: ILM intact + inner retina bridging + partial thickness = Lamellar hole. This does NOT warrant vitrectomy unless symptomatic. Watzke-Allen test is negative (beam uniformly distorted, not broken).

Key teaching point: Pseudo-operculum = avulsed inner retinal tissue still attached to the posterior hyaloid. When the hyaloid fully separates (Stage 4), the operculum becomes truly free-floating. The presence of cystic spaces flanking the hole is the hallmark OCT appearance of established FTMH.

Key teaching point for MCQ: Larger holes with wider cystic areas have poorer surgical prognosis. AI segmentation tools now automatically measure MLD and basal diameter for IVTS classification. The cystic area correlates with pre-operative hole stage and post-op visual recovery.

| Structure | What You See | Clinical Meaning |
|---|---|---|
| ILM | Thin bright top line | Intact = lamellar/pseudohole; Breached = FTMH |
| Intraretinal cysts | Dark rounded spaces in retina | Stage 1b/2/3 — schisis cavity |
| Operculum | Bright oval above hole | True FTMH confirmed; pseudo = still attached to hyaloid |
| SRF | Dark crescent at hole base | Stages 3/4; resolves after surgery |
| Posterior hyaloid | Bright line above retina | Solid = attached (VMT); Separated = PVD |
| IS/OS (ellipsoid zone) | Bright outer band | Intact = good VA prognosis; Disrupted = poor |
| RPE | Outermost bright band | Intact under hole = better recovery |
| Hole size (MLD) | Narrowest point | <250 µm = small; 250-400 = medium; >400 = large |
Endophthalmitis
endophthalmitis clinical photo hypopyon anterior chamber infection

This appearance — post-cataract surgery, rapid onset pain + redness + visual loss + hypopyon — is a ophthalmic emergency requiring immediate intravitreal antibiotics

Hypopyon = pus in anterior chamber = gravity-dependent white layer. In endophthalmitis it is typically large and rapidly progressing. Distinguish from sterile hypopyon (HLA-B27 uveitis, Behçet's) — but treat as infection until proven otherwise.
| Parameter | Detail |
|---|---|
| Definition | Severe intraocular infection involving vitreous and/or aqueous humor |
| Incidence post-cataract | ~0.1% (varies by study) |
| Most common organism | Staphylococcus epidermidis (Gram-positive, ~90% of isolates) |
| Gram-negative | ~10% of isolates (includes Pseudomonas aeruginosa — more virulent) |
| Pathogenesis | Rapid photoreceptor damage from bacterial toxins + host inflammatory response |
| Sign | Significance |
|---|---|
| Eyelid swelling + chemosis | Periorbital inflammation |
| Conjunctival injection + discharge | Severe ocular surface inflammation |
| Corneal haze / oedema | Endotoxin damage to endothelium |
| Fibrinous exudate + Hypopyon | Pus in AC — hallmark sign |
| Severe anterior uveitis | Anterior segment involvement |
| Vitritis + impaired fundus view | Vitreous involvement |
| Loss of red reflex | Severe vitreous opacification |
| Relative afferent pupillary defect (RAPD) | Poor prognostic sign — retinal ischaemia |
| Risk Factor | Example |
|---|---|
| Intraoperative | Posterior capsule rupture, prolonged surgery, vitreous loss |
| Incision type | Clear corneal sutureless incision (temporal > superior) |
| Wound-related | Wound leak on day 1 |
| Patient factors | Diabetes, blepharitis, dacryocystitis, adnexal infection |
| Other | Delayed topical antibiotics, topical anaesthesia only |
TIP (Kanski): Preoperative instillation of 5% povidone-iodine reduces the risk of endophthalmitis. In a one-eyed patient — ensure the adjacent socket is free of infection before cataract surgery.
| Sample | Method | Notes |
|---|---|---|
| Aqueous | 0.1-0.2 ml via limbal paracentesis, 25G needle | Labelled syringe |
| Vitreous | 0.2-0.4 ml, 23G needle or disposable vitrector, 3.5 mm from limbus | More likely positive than aqueous |
| Conjunctival swab | Additional | If intraocular samples negative |
| Drug | Dose | Covers |
|---|---|---|
| Vancomycin | 2 mg in 0.1 ml | Gram-positive cocci including MRSA |
| Ceftazidime | 2 mg in 0.1 ml | Gram-negative including Pseudomonas |
| Amikacin | 0.4 mg in 0.1 ml | Alternative to ceftazidime (penicillin allergy) — more retinal toxic |
| Treatment | Detail |
|---|---|
| Repeat intravitreal antibiotics at 48 hours | If no improvement |
| Topical steroids | Start after 48 hours (not before — let antibiotics work) |
| Oral fluoroquinolones | 10-14 days |
| Topical antibiotics (intensive) | Concurrent |
| Cycloplegic | For pain + prevent posterior synechiae |
| Condition | EVS Recommendation |
|---|---|
| VA = light perception (LP) | Immediate vitrectomy + intravitreal antibiotics |
| VA = hand movements (HM) or better | Intravitreal antibiotics alone (vitrectomy NOT superior) |
| No improvement at 36-48 hours | Consider vitrectomy regardless |
| Fistula-related endophthalmitis | Aggressive early vitrectomy recommended |
EVS KEY POINT: If initial VA is light perception only → immediate PPV. If VA is HM or better → intravitreal antibiotics alone first. Vitrectomy allows better antibiotic penetration and removal of bacterial load.
| Condition | Key Distinguishing Feature |
|---|---|
| Retained lens material | No fever, may have raised IOP; OCT/UBM shows lens fragments |
| Vitreous haemorrhage | Depigmented blood in vitreous; no pain/fever; no hypopyon |
| Postoperative uveitis (sterile) | Mild signs; responds to topical steroids within 6-24 hours |
| Toxic anterior segment syndrome (TASS) | Intense fibrin reaction, corneal oedema; no vitreous involvement; due to contaminated irrigating fluid/viscoelastic |
| Sympathetic ophthalmia | After penetrating trauma; bilateral; granulomatous uveitis |
If any doubt — treat as infectious endophthalmitis immediately.
| Organism | Associations |
|---|---|
| Candida albicans | IV drug use, prolonged IV lines, immunosuppression |
| Aspergillus | Neutropenia, bone marrow transplant, haematological malignancy |
| Klebsiella pneumoniae | Liver abscess — East Asian patients (classic association) |
| Bacillus cereus | Post-traumatic — soil, vegetable matter; extremely aggressive |
| S. aureus | Bacteraemia, IV drug use |
| Point | Answer |
|---|---|
| Most common organism post-cataract | Staphylococcus epidermidis |
| Gram-positive : Gram-negative ratio | 90% : 10% |
| Hypopyon definition | Pus (gravity-dependent) in anterior chamber |
| Pre-op prophylaxis — gold standard | 5% povidone-iodine to conjunctival fornices (≥3 minutes) |
| Intracameral prophylaxis | Cefuroxime 1 mg in 0.1 ml at end of surgery |
| AVOID as intracameral prophylaxis | Vancomycin — causes haemorrhagic occlusive retinal vasculitis |
| Most likely positive culture site | Vitreous (> aqueous) |
| Vitreous tap site in pseudophakic eye | 3.5 mm from limbus |
| Intravitreal vancomycin dose | 2 mg in 0.1 ml |
| Intravitreal ceftazidime dose | 2 mg in 0.1 ml |
| Amikacin dose + concern | 0.4 mg in 0.1 ml — more retinal toxic |
| EVS: immediate vitrectomy when VA = | Light perception (LP) |
| EVS: antibiotics alone when VA = | Hand movements or better |
| When to start topical steroids | After 48 hours (not before) |
| Oral antibiotics duration | 10-14 days fluoroquinolones |
| Most virulent post-traumatic organism | Bacillus cereus |
| Chronic endophthalmitis organism | Propionibacterium acnes (white capsular plaque) |
| Endogenous + liver abscess + East Asian | Klebsiella pneumoniae |
| Fistula-related endophthalmitis prognosis | Poor — more virulent organisms than post-cataract |
| TASS vs endophthalmitis distinction | TASS: no vitreous involvement; responds to steroids |
| PCR limitation | High sensitivity → false positives (contamination) |
Create a visual timeline/decision tree for endophthalmitis management (EVS criteria)
python3 << 'PYEOF' import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt import matplotlib.patches as mpatches from matplotlib.patches import FancyBboxPatch, FancyArrowPatch from matplotlib.lines import Line2D import numpy as np fig = plt.figure(figsize=(24, 28), facecolor='#0A1628') ax = fig.add_axes([0, 0, 1, 1]) ax.set_xlim(0, 24); ax.set_ylim(0, 28); ax.axis('off') # ── Palette ────────────────────────────────────────────────────── BG = '#0A1628'; PANEL = '#112240'; PANEL2 = '#0D1B36' RED = '#E53935'; RED_L = '#FFEBEE'; RED_D = '#B71C1C' ORANGE = '#FB8C00'; ORG_L = '#FFF3E0' GREEN = '#43A047'; GRN_L = '#E8F5E9'; GRN_D = '#1B5E20' BLUE = '#1E88E5'; BLU_L = '#E3F2FD'; BLU_D = '#0D47A1' TEAL = '#00897B'; TEL_L = '#E0F2F1' PURPLE = '#8E24AA'; PUR_L = '#F3E5F5' AMBER = '#FFB300'; AMB_L = '#FFF8E1' GREY = '#546E7A'; GRY_L = '#ECEFF1' WHITE = '#ECEFF4'; SUB = '#90A4AE' YELLOW = '#FDD835' def rbox(ax, x, y, w, h, fc, ec=None, lw=1.5, alpha=1.0, rad=0.2, zorder=3): p = FancyBboxPatch((x, y), w, h, boxstyle=f"round,pad={rad}", facecolor=fc, edgecolor=ec or fc, linewidth=lw, alpha=alpha, zorder=zorder) ax.add_patch(p) def txt(ax, x, y, t, fs=10, c=WHITE, bold=False, ha='center', va='center', zorder=8, wrap=False): fw = 'bold' if bold else 'normal' ax.text(x, y, t, fontsize=fs, color=c, fontweight=fw, ha=ha, va=va, zorder=zorder, wrap=wrap, multialignment='center' if ha == 'center' else 'left') def arrow(ax, x1, y1, x2, y2, c=WHITE, lw=2, style='->', head=12, zorder=6): ax.annotate('', xy=(x2, y2), xytext=(x1, y1), arrowprops=dict(arrowstyle=style, color=c, lw=lw, mutation_scale=head), zorder=zorder) def diamond(ax, cx, cy, w, h, fc, ec, lw=2, zorder=4): dx = w/2; dy = h/2 pts = np.array([[cx, cy+dy],[cx+dx, cy],[cx, cy-dy],[cx-dx, cy]]) poly = plt.Polygon(pts, facecolor=fc, edgecolor=ec, linewidth=lw, zorder=zorder) ax.add_patch(poly) def badge(ax, x, y, txt_str, fc, ec, fs=8.5, zorder=9): rbox(ax, x-0.55, y-0.2, 1.1, 0.42, fc, ec, lw=1.2, rad=0.1, zorder=zorder) txt(ax, x, y+0.01, txt_str, fs=fs, c=WHITE, bold=True, zorder=zorder+1) # ══════════════════════════════════════════════════════════════════ # TITLE # ══════════════════════════════════════════════════════════════════ rbox(ax, 0.3, 26.5, 23.4, 1.25, PANEL, BLUE, lw=2.5, rad=0.25) txt(ax, 12, 27.25, 'ENDOPHTHALMITIS MANAGEMENT', fs=21, c=WHITE, bold=True) txt(ax, 12, 26.78, 'Decision Tree · EVS Criteria · Treatment Protocol | Kanski 10th Ed. pp. 343–347 · Endophthalmitis Vitrectomy Study', fs=10.5, c='#90CAF9') # ══════════════════════════════════════════════════════════════════ # STEP 1 — CLINICAL SUSPICION (top) # ══════════════════════════════════════════════════════════════════ rbox(ax, 8.0, 24.8, 8.0, 1.45, '#1A3A5C', BLUE, lw=2, rad=0.2) txt(ax, 12, 25.7, 'CLINICAL SUSPICION OF ENDOPHTHALMITIS', fs=13, c=WHITE, bold=True) txt(ax, 12, 25.22, 'Pain · Redness · Visual loss · Hypopyon · Vitritis · Loss of red reflex', fs=9.5, c='#90CAF9') arrow(ax, 12, 24.8, 12, 24.2, c=BLUE, lw=2.5, head=14) # ══════════════════════════════════════════════════════════════════ # STEP 2 — IMMEDIATE ACTIONS BOX # ══════════════════════════════════════════════════════════════════ rbox(ax, 3.0, 22.4, 18.0, 1.65, '#1A2744', ORANGE, lw=2, rad=0.2) txt(ax, 12, 23.52, '⚡ IMMEDIATE ACTIONS (Do Not Delay)', fs=12, c=AMBER, bold=True) badge(ax, 4.5, 22.95, 'Step 1', ORANGE, '#E65100', fs=8) txt(ax, 12.0, 22.95, 'B-scan ultrasound if no view · Obtain aqueous (0.1–0.2 ml, 25G, limbal paracentesis) · Obtain vitreous (0.2–0.4 ml, 23G or vitrector, 3.5 mm from limbus)', fs=9, c=WHITE, bold=False) txt(ax, 12.0, 22.6, 'Send specimens to microbiology IMMEDIATELY · Then give intravitreal antibiotics', fs=8.8, c='#FFCC80') arrow(ax, 12, 22.4, 12, 21.75, c=ORANGE, lw=2.5, head=14) # ══════════════════════════════════════════════════════════════════ # STEP 3 — INTRAVITREAL ANTIBIOTICS (always) # ══════════════════════════════════════════════════════════════════ rbox(ax, 3.5, 20.2, 17.0, 1.4, '#1A3A1A', GREEN, lw=2, rad=0.2) txt(ax, 12, 21.2, '💉 INTRAVITREAL ANTIBIOTICS (Mandatory — Given Immediately After Cultures)', fs=12, c='#A5D6A7', bold=True) badge(ax, 5.0, 20.65, 'Step 2', GREEN, GRN_D, fs=8) txt(ax, 12, 20.65, 'Vancomycin 2 mg/0.1 ml (Gram-positive, MRSA) + Ceftazidime 2 mg/0.1 ml (Gram-negative, Pseudomonas)', fs=9.5, c=WHITE) txt(ax, 12, 20.32, 'Amikacin 0.4 mg/0.1 ml = alternative to ceftazidime if true penicillin allergy (MORE retinal toxic — use with caution)', fs=8.5, c='#FFCC80') arrow(ax, 12, 20.2, 12, 19.55, c=GREEN, lw=2.5, head=14) # ══════════════════════════════════════════════════════════════════ # EVS DECISION DIAMOND # ══════════════════════════════════════════════════════════════════ diamond(ax, 12, 18.4, 6.5, 1.85, '#1A1A3A', PURPLE, lw=2.5, zorder=4) txt(ax, 12, 18.68, 'EVS CRITERION', fs=12, c='#CE93D8', bold=True, zorder=8) txt(ax, 12, 18.22, 'What is the presenting visual acuity?', fs=10, c=WHITE, zorder=8) # Labels on diamond sides txt(ax, 5.2, 18.4, 'VA = LIGHT\nPERCEPTION (LP)\nor worse', fs=10, c=RED, bold=True, ha='right') txt(ax, 18.8, 18.4, 'VA = HAND\nMOVEMENTS (HM)\nor better', fs=10, c=GREEN, bold=True, ha='left') # Arrows from diamond arrow(ax, 8.75, 18.4, 6.5, 18.4, c=RED, lw=2.5, head=14) # left → vitrectomy arrow(ax, 15.25, 18.4, 17.5, 18.4, c=GREEN, lw=2.5, head=14) # right → antibiotics only # ══════════════════════════════════════════════════════════════════ # LEFT BRANCH — LP: IMMEDIATE VITRECTOMY # ══════════════════════════════════════════════════════════════════ rbox(ax, 0.4, 15.6, 7.8, 2.55, '#2C1010', RED, lw=2.5, rad=0.2) txt(ax, 4.3, 17.9, '🔴 IMMEDIATE PPV', fs=13, c=RED, bold=True) txt(ax, 4.3, 17.5, '(Pars Plana Vitrectomy)', fs=10, c='#FFCDD2') badge(ax, 1.5, 16.95, 'EVS LP', RED, RED_D, fs=8) items_l = [ '• VA = Light Perception → immediate vitrectomy', '• EVS: 3× better outcome vs tap-inject alone', '• Removes bacterial load + toxins directly', '• Better antibiotic penetration post-vitrectomy', '• Repeat intravitreal antibiotics intraoperatively', ] ly = 16.88 for it in items_l: txt(ax, 4.3, ly, it, fs=8.8, c=WHITE, ha='center') ly -= 0.36 # ══════════════════════════════════════════════════════════════════ # RIGHT BRANCH — HM: ANTIBIOTICS ALONE # ══════════════════════════════════════════════════════════════════ rbox(ax, 15.8, 15.6, 7.8, 2.55, '#0D2E0D', GREEN, lw=2.5, rad=0.2) txt(ax, 19.7, 17.9, '🟢 INTRAVITREAL', fs=13, c=GREEN, bold=True) txt(ax, 19.7, 17.5, 'ANTIBIOTICS ALONE', fs=10, c='#C8E6C9') badge(ax, 16.8, 16.95, 'EVS HM', GREEN, GRN_D, fs=8) items_r = [ '• VA = HM or better → tap + inject only', '• EVS: vitrectomy NOT superior at this VA level', '• Intravitreal vancomycin + ceftazidime', '• Monitor closely — reassess at 36-48 hours', '• Escalate to vitrectomy if no improvement', ] ry = 16.88 for it in items_r: txt(ax, 19.7, ry, it, fs=8.8, c=WHITE, ha='center') ry -= 0.36 # arrows down from branches arrow(ax, 4.3, 15.6, 4.3, 15.0, c=RED, lw=2, head=10) arrow(ax, 19.7, 15.6, 19.7, 15.0, c=GREEN, lw=2, head=10) # ══════════════════════════════════════════════════════════════════ # REASSESSMENT AT 48 HOURS (both branches join) # ══════════════════════════════════════════════════════════════════ rbox(ax, 3.0, 13.3, 18.0, 1.55, '#1A2744', BLUE, lw=2, rad=0.2) txt(ax, 12, 14.52, '⏱ REASSESS AT 48 HOURS', fs=12, c='#90CAF9', bold=True) badge(ax, 4.5, 14.0, 'Step 3', BLUE, BLU_D, fs=8) txt(ax, 12, 14.0, 'Repeat intravitreal antibiotics · Start TOPICAL STEROIDS (not before 48 hrs) · Oral fluoroquinolones × 10–14 days', fs=9.5, c=WHITE) txt(ax, 12, 13.62, 'Consider PPV if: No clinical improvement · Worsening despite antibiotics · Fistula-related endophthalmitis · Very virulent organism', fs=8.8, c='#FFCC80') # Join arrows from both sides to 48hr box ax.annotate('', xy=(4.3, 13.85), xytext=(4.3, 15.0), arrowprops=dict(arrowstyle='->', color=RED, lw=1.8, mutation_scale=10), zorder=6) ax.annotate('', xy=(19.7, 13.85), xytext=(19.7, 15.0), arrowprops=dict(arrowstyle='->', color=GREEN, lw=1.8, mutation_scale=10), zorder=6) # Horizontal connectors to box ax.plot([4.3, 4.3], [13.85, 13.85], color=RED, lw=1.5, zorder=5) ax.plot([19.7, 19.7], [13.85, 13.85], color=GREEN, lw=1.5, zorder=5) arrow(ax, 12, 13.3, 12, 12.65, c=BLUE, lw=2.5, head=14) # ══════════════════════════════════════════════════════════════════ # SPECIFIC TYPES — 3 COLUMN PANELS # ══════════════════════════════════════════════════════════════════ rbox(ax, 0.3, 11.1, 23.4, 0.48, '#1A2744', TEAL, lw=1.5, rad=0.1) txt(ax, 12, 11.34, 'ENDOPHTHALMITIS BY TYPE — Classification & Organism', fs=11, c='#80CBC4', bold=True) types = [ # (x, title, title_col, badge, items, badge_col) (0.4, 'ACUTE POST-OP', RED, [('Onset','Days 2–7 post cataract surgery'), ('Organisms','S. epidermidis (most common)\nS. aureus · Streptococcus'), ('Gram ratio','~90% Gram-positive'), ('Prognosis','Reasonable with early Rx'), ('EVS Trial','Landmark RCT — applies here')]), (8.3, 'CHRONIC / DELAYED', PURPLE, [('Onset','Weeks–months post-op'), ('Organisms','Propionibacterium acnes\nFungi · S. epidermidis'), ('Sign','White capsular plaque'), ('Rx','Vitrectomy + total capsulectomy\n+ IVT vancomycin'), ('Key','PCR for diagnosis')]), (16.2, 'POST-TRAUMATIC', ORANGE, [('Onset','After penetrating injury'), ('Classic organism','Bacillus cereus\n(soil/vegetable matter)'), ('Prognosis','VERY POOR — often evisceration'), ('Others','S. aureus · Fungi'), ('Rx','Emergency vitrectomy\n+ broad-spectrum IVT abx')]), ] for (bx, title, col, items) in types: rbox(ax, bx, 8.35, 7.5, 2.6, PANEL2, col, lw=1.8, rad=0.18) rbox(ax, bx, 10.6, 7.5, 0.42, col, col, lw=0, rad=0.1, alpha=0.9) txt(ax, bx+3.75, 10.81, title, fs=10.5, c=WHITE, bold=True) iy = 10.45 for (k, v) in items: txt(ax, bx+0.25, iy, f'{k}:', fs=8.5, c=col, bold=True, ha='left') txt(ax, bx+2.5, iy, v, fs=8.5, c=WHITE, ha='left') iy -= (0.5 if '\n' in v else 0.44) # Endogenous panel (full width) rbox(ax, 0.3, 6.55, 23.4, 1.68, PANEL, TEAL, lw=1.8, rad=0.18) rbox(ax, 0.3, 7.92, 23.4, 0.42, TEAL, TEAL, lw=0, rad=0.1) txt(ax, 12, 8.13, 'ENDOGENOUS ENDOPHTHALMITIS — Haematogenous spread (no surgery/trauma)', fs=10.5, c=WHITE, bold=True) endo = [ ('Candida', 'IV drug use · prolonged IV lines · ICU · immunosuppressed'), ('Aspergillus', 'Neutropenia · BMT · haematological malignancy'), ('Klebsiella', 'Liver abscess → EAST ASIAN patients (classic PG MCQ association)'), ('S. aureus', 'Bacteraemia · septicaemia · IV drug use'), ('Bilateral?', '~25% bilateral · Risk: DM, renal failure, immunocompromised'), ] ex = 0.7; ey = 7.75 for org, assoc in endo: txt(ax, ex+0.0, ey, f'• {org}:', fs=9, c='#80CBC4', bold=True, ha='left') txt(ax, ex+2.0, ey, assoc, fs=9, c=WHITE, ha='left') ey -= 0.38 # ══════════════════════════════════════════════════════════════════ # PROPHYLAXIS STRIP # ══════════════════════════════════════════════════════════════════ rbox(ax, 0.3, 4.85, 23.4, 1.52, '#1A1A00', AMBER, lw=2, rad=0.2) txt(ax, 12, 6.1, '🛡 PROPHYLAXIS — Pre/Intraoperative (Post-cataract)', fs=11, c=AMBER, bold=True) proph = [ ('✅ GOLD STANDARD', '5% Povidone-iodine into conjunctival fornix — ≥3 minutes before surgery'), ('✅ INTRACAMERAL', 'Cefuroxime 1 mg/0.1 ml injected into AC at END of surgery'), ('✅ ALTERNATIVE', 'Moxifloxacin 0.5 mg/0.1 ml intracameral (if cefuroxime unavailable)'), ('❌ AVOID', 'Intracameral vancomycin → HAEMORRHAGIC OCCLUSIVE RETINAL VASCULITIS'), ('⚠ LACKING EVIDENCE', 'Preoperative topical fluoroquinolones — commonly used but no proven efficacy'), ] px = 0.6; py = 5.78 for label, detail in proph: col = RED if '❌' in label else (ORANGE if '⚠' in label else GREEN) txt(ax, px, py, label, fs=8.5, c=col, bold=True, ha='left') txt(ax, px+3.8, py, detail, fs=8.8, c=WHITE, ha='left') py -= 0.36 # ══════════════════════════════════════════════════════════════════ # MCQ STRIP # ══════════════════════════════════════════════════════════════════ rbox(ax, 0.3, 2.45, 23.4, 2.22, '#200010', RED, lw=2, rad=0.2) txt(ax, 12, 4.38, '🎯 HIGH-YIELD PG MCQ POINTS', fs=11, c='#EF9A9A', bold=True) mcqs = [ ('Most common organism', 'S. epidermidis'), ('Gram +ve : Gram -ve', '90% : 10%'), ('EVS — immediate vitrectomy when', 'VA = Light Perception (LP)'), ('EVS — antibiotics alone when', 'VA = HM or better'), ('When to start topical steroids', 'AFTER 48 hours (not before)'), ('Vancomycin dose (IVT)', '2 mg in 0.1 ml'), ('Ceftazidime dose (IVT)', '2 mg in 0.1 ml'), ('Vitreous tap entry point', '3.5 mm from limbus (pseudophakic)'), ('Avoid as intracameral prophylaxis', 'Vancomycin → haemorrhagic retinal vasculitis'), ('Post-traumatic classic organism', 'Bacillus cereus'), ('Chronic/delayed organism', 'Propionibacterium acnes (capsular plaque)'), ('Endogenous + liver abscess', 'Klebsiella pneumoniae (East Asian)'), ] mx1 = 0.6; mx2 = 8.4; mx3 = 12.6; mx4 = 20.4; my = 4.05 for i, (q, a) in enumerate(mcqs): if i < 6: cx = mx1; ax2_x = mx2 else: cx = mx3; ax2_x = mx4 row_y = my - (i % 6) * 0.29 txt(ax, cx, row_y, f'• {q}:', fs=8.2, c=SUB, ha='left') txt(ax, ax2_x, row_y, a, fs=8.2, c=AMBER, bold=True, ha='left') # ══════════════════════════════════════════════════════════════════ # FOOTER # ══════════════════════════════════════════════════════════════════ txt(ax, 12, 0.5, "Source: Kanski's Clinical Ophthalmology 10th Ed. pp. 343–347 · Endophthalmitis Vitrectomy Study (EVS, 1995) · Wills Eye Manual", fs=8.5, c=SUB) plt.savefig('/tmp/workspace/ffa-pdf/Endophthalmitis_Decision_Tree.png', dpi=150, bbox_inches='tight', facecolor=BG) plt.close() print("Done") PYEOF
python3 << 'PYEOF' import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt import matplotlib.patches as mpatches from matplotlib.patches import FancyBboxPatch import numpy as np fig = plt.figure(figsize=(24, 28), facecolor='#0A1628') ax = fig.add_axes([0, 0, 1, 1]) ax.set_xlim(0, 24); ax.set_ylim(0, 28); ax.axis('off') BG='#0A1628'; PANEL='#112240'; PANEL2='#0D1B36' RED='#E53935'; RED_D='#B71C1C' ORANGE='#FB8C00' GREEN='#43A047'; GRN_D='#1B5E20' BLUE='#1E88E5'; BLU_D='#0D47A1' TEAL='#00897B' PURPLE='#8E24AA' AMBER='#FFB300' WHITE='#ECEFF4'; SUB='#90A4AE' def rbox(ax,x,y,w,h,fc,ec=None,lw=1.5,alpha=1.0,rad=0.2,zorder=3): p=FancyBboxPatch((x,y),w,h,boxstyle=f"round,pad={rad}", facecolor=fc,edgecolor=ec or fc, linewidth=lw,alpha=alpha,zorder=zorder) ax.add_patch(p) def txt(ax,x,y,t,fs=10,c=WHITE,bold=False,ha='center',va='center',zorder=8): ax.text(x,y,t,fontsize=fs,color=c,fontweight='bold'if bold else 'normal', ha=ha,va=va,zorder=zorder,multialignment='center'if ha=='center'else 'left') def arrow(ax,x1,y1,x2,y2,c=WHITE,lw=2,head=12,zorder=6): ax.annotate('',xy=(x2,y2),xytext=(x1,y1), arrowprops=dict(arrowstyle='->',color=c,lw=lw,mutation_scale=head),zorder=zorder) def diamond(ax,cx,cy,w,h,fc,ec,lw=2.5,zorder=4): dx=w/2; dy=h/2 pts=np.array([[cx,cy+dy],[cx+dx,cy],[cx,cy-dy],[cx-dx,cy]]) poly=plt.Polygon(pts,facecolor=fc,edgecolor=ec,linewidth=lw,zorder=zorder) ax.add_patch(poly) def badge(ax,x,y,t,fc,ec,fs=8.5,zorder=9): rbox(ax,x-0.55,y-0.21,1.1,0.44,fc,ec,lw=1.2,rad=0.1,zorder=zorder) txt(ax,x,y+0.01,t,fs=fs,c=WHITE,bold=True,zorder=zorder+1) # ── TITLE ──────────────────────────────────────────────────────── rbox(ax,0.3,26.5,23.4,1.25,PANEL,BLUE,lw=2.5,rad=0.25) txt(ax,12,27.25,'ENDOPHTHALMITIS: MANAGEMENT DECISION TREE',fs=20,c=WHITE,bold=True) txt(ax,12,26.78,'EVS Criteria | Treatment Protocol | Classification | Kanski 10th Ed.',fs=10.5,c='#90CAF9') # ── CLINICAL SUSPICION ─────────────────────────────────────────── rbox(ax,7.5,24.75,9.0,1.5,'#1A3A5C',BLUE,lw=2,rad=0.2) txt(ax,12,25.68,'SUSPECT ENDOPHTHALMITIS',fs=13,c=WHITE,bold=True) txt(ax,12,25.2,'Pain + Redness + Visual loss + Hypopyon + Vitritis + Loss of red reflex',fs=9.5,c='#90CAF9') arrow(ax,12,24.75,12,24.12,c=BLUE,lw=2.5,head=14) # ── STEP 1: IMMEDIATE ACTIONS ──────────────────────────────────── rbox(ax,2.5,22.35,19.0,1.65,'#1A2020',ORANGE,lw=2.2,rad=0.2) badge(ax,3.6,23.6,'STEP 1',ORANGE,'#E65100') txt(ax,12,23.6,'IMMEDIATE ACTIONS — Do Not Delay',fs=12,c=AMBER,bold=True) txt(ax,12,23.18,'B-scan US if no view | Aqueous tap: 0.1-0.2 ml via limbal paracentesis (25G) | Vitreous tap: 0.2-0.4 ml via vitrector/23G, 3.5 mm from limbus',fs=9,c=WHITE) txt(ax,12,22.78,'Send specimens to microbiology IMMEDIATELY | Then administer intravitreal antibiotics without delay',fs=8.8,c='#FFCC80') arrow(ax,12,22.35,12,21.7,c=ORANGE,lw=2.5,head=14) # ── STEP 2: IVT ANTIBIOTICS ────────────────────────────────────── rbox(ax,2.5,20.15,19.0,1.45,'#0D2010',GREEN,lw=2.2,rad=0.2) badge(ax,3.6,21.23,'STEP 2',GREEN,GRN_D) txt(ax,12,21.23,'INTRAVITREAL ANTIBIOTICS (Always — given immediately after cultures)',fs=12,c='#A5D6A7',bold=True) txt(ax,12,20.78,'VANCOMYCIN 2 mg / 0.1 ml (Gram-positive, MRSA) + CEFTAZIDIME 2 mg / 0.1 ml (Gram-negative incl. Pseudomonas)',fs=10,c=WHITE) txt(ax,12,20.38,'Alternative to ceftazidime: Amikacin 0.4 mg/0.1 ml (penicillin allergy only — MORE retinal toxic)',fs=8.8,c='#FFCC80') arrow(ax,12,20.15,12,19.45,c=GREEN,lw=2.5,head=14) # ── EVS DIAMOND ────────────────────────────────────────────────── diamond(ax,12,18.3,7.0,1.95,'#1A1030',PURPLE,lw=3,zorder=4) txt(ax,12,18.62,'EVS DECISION',fs=13,c='#CE93D8',bold=True,zorder=8) txt(ax,12,18.18,'What is the presenting visual acuity?',fs=10,c=WHITE,zorder=8) # Side labels rbox(ax,0.5,17.62,4.8,1.35,'#2C0808',RED,lw=2,rad=0.18) txt(ax,2.9,18.42,'VA = LIGHT\nPERCEPTION',fs=11,c=RED,bold=True) txt(ax,2.9,17.85,'(LP or worse)',fs=9.5,c='#FFCDD2') rbox(ax,18.7,17.62,4.8,1.35,'#082C08',GREEN,lw=2,rad=0.18) txt(ax,21.1,18.42,'VA = HAND\nMOVEMENTS',fs=11,c=GREEN,bold=True) txt(ax,21.1,17.85,'(HM or better)',fs=9.5,c='#C8E6C9') # Horizontal arrows from diamond arrow(ax,8.5,18.3,5.3,18.3,c=RED,lw=3,head=14) arrow(ax,15.5,18.3,18.7,18.3,c=GREEN,lw=3,head=14) # ── LEFT: VITRECTOMY ───────────────────────────────────────────── rbox(ax,0.3,14.8,8.8,2.7,'#2C0808',RED,lw=2.5,rad=0.2) txt(ax,4.7,17.22,'IMMEDIATE VITRECTOMY (PPV)',fs=12,c=RED,bold=True) txt(ax,4.7,16.85,'Pars Plana Vitrectomy',fs=9.5,c='#FFCDD2') badge(ax,1.5,16.38,'EVS: LP',RED,RED_D) evs_l=[ '• VA = LP --> immediate PPV (EVS recommendation)', '• 3x better visual outcome vs tap-inject alone', '• Removes bacterial load and toxins directly', '• Better antibiotic penetration post-vitrectomy', '• Repeat intravitreal antibiotics intraoperatively', ] ly=16.38 for it in evs_l: txt(ax,4.7,ly,it,fs=8.8,c=WHITE); ly-=0.38 # ── RIGHT: ANTIBIOTICS ALONE ───────────────────────────────────── rbox(ax,14.9,14.8,8.8,2.7,'#082C08',GREEN,lw=2.5,rad=0.2) txt(ax,19.3,17.22,'INTRAVITREAL ANTIBIOTICS ALONE',fs=11.5,c=GREEN,bold=True) txt(ax,19.3,16.85,'(No immediate vitrectomy)',fs=9.5,c='#C8E6C9') badge(ax,16.1,16.38,'EVS: HM',GREEN,GRN_D) evs_r=[ '• VA = HM or better --> tap + inject only', '• EVS: vitrectomy NOT superior at this VA level', '• Intravitreal vancomycin + ceftazidime', '• Monitor closely — review at 36-48 hours', '• Escalate to vitrectomy if no improvement', ] ry=16.38 for it in evs_r: txt(ax,19.3,ry,it,fs=8.8,c=WHITE); ry-=0.38 # ── STEP 3: REASSESS 48h ───────────────────────────────────────── arrow(ax,4.7,14.8,4.7,14.2,c=RED,lw=2,head=10) arrow(ax,19.3,14.8,19.3,14.2,c=GREEN,lw=2,head=10) # lines to box edges ax.plot([4.7,4.7],[14.2,14.2],color=RED,lw=1.5,zorder=5) ax.plot([19.3,19.3],[14.2,14.2],color=GREEN,lw=1.5,zorder=5) rbox(ax,2.5,12.3,19.0,1.8,'#0A1E30',BLUE,lw=2,rad=0.2) badge(ax,3.6,13.78,'STEP 3',BLUE,BLU_D) txt(ax,12,13.78,'REASSESS AT 48 HOURS',fs=12,c='#90CAF9',bold=True) txt(ax,12,13.35,'Repeat intravitreal antibiotics | Start TOPICAL STEROIDS (start AFTER 48 hrs — not before)',fs=9.5,c=WHITE) txt(ax,12,12.95,'Oral fluoroquinolones x 10-14 days | Consider PPV if: no improvement / worsening / fistula-related / virulent organism',fs=8.8,c='#FFCC80') txt(ax,12,12.55,'Add cycloplegic agent (atropine/cyclopentolate) for pain + prevent posterior synechiae',fs=8.5,c=SUB) arrow(ax,12,12.3,12,11.7,c=BLUE,lw=2,head=12) # ── CLASSIFICATION TABLE ───────────────────────────────────────── rbox(ax,0.3,10.05,23.4,0.52,PANEL,TEAL,lw=1.5,rad=0.12) txt(ax,12,10.31,'ENDOPHTHALMITIS — TYPE / ORGANISM / MANAGEMENT SUMMARY',fs=11,c='#80CBC4',bold=True) cols_x=[0.4,8.2,16.1] types=[ ('ACUTE POST-OP',RED,[ ('Onset','Days 2-7 post cataract surgery'), ('Organisms','S. epidermidis (most common)'), ('','S. aureus | Streptococcus spp.'), ('Gram ratio','~90% Gram-positive'), ('EVS applies','Yes — landmark RCT for this type'), ('Prognosis','Good if treated early')]), ('CHRONIC / DELAYED',PURPLE,[ ('Onset','Weeks to months post-op'), ('Organisms','Propionibacterium acnes (key)'), ('Sign','White intracapsular plaque'), ('Diagnosis','PCR + culture of capsule'), ('Rx','Vitrectomy + TOTAL capsulectomy'), ('','+ IVT vancomycin')]), ('POST-TRAUMATIC',ORANGE,[ ('Classic organism','Bacillus cereus (soil/vegetable)'), ('Prognosis','VERY POOR --> often evisceration'), ('Others','S. aureus | Fungi'), ('Onset','Within 24-48 hrs of injury'), ('Rx','Emergency vitrectomy'), ('','+ broad-spectrum IVT antibiotics')]), ] for (bx,(title,col,items)) in zip(cols_x,types): rbox(ax,bx,7.1,7.5,2.82,PANEL2,col,lw=1.8,rad=0.18) rbox(ax,bx,9.62,7.5,0.40,col,col,lw=0,rad=0.1) txt(ax,bx+3.75,9.82,title,fs=10.5,c=WHITE,bold=True) iy=9.45 for k,v in items: if k: txt(ax,bx+0.2,iy,f'{k}:',fs=8.5,c=col,bold=True,ha='left') txt(ax,bx+2.5,iy,v,fs=8.5,c=WHITE,ha='left') else: txt(ax,bx+2.5,iy,v,fs=8.5,c=SUB,ha='left') iy-=0.43 # ── ENDOGENOUS ROW ─────────────────────────────────────────────── rbox(ax,0.3,5.55,23.4,1.35,PANEL,TEAL,lw=1.8,rad=0.18) rbox(ax,0.3,6.6,23.4,0.38,TEAL,TEAL,lw=0,rad=0.1) txt(ax,12,6.79,'ENDOGENOUS ENDOPHTHALMITIS (Haematogenous — no surgery/trauma)',fs=11,c=WHITE,bold=True) endo=[ ('Candida albicans','IV drug use | prolonged IV catheters | immunosuppressed | bilateral 25%'), ('Aspergillus','Neutropenia | bone marrow transplant | haematological malignancy'), ('Klebsiella pneumoniae','Liver abscess --> EAST ASIAN PATIENTS (classic PG MCQ association)'), ('S. aureus / others','Septicaemia | IV drug use | DM | renal failure'), ] ex=0.6; ey=6.43 for org,assoc in endo: txt(ax,ex,ey,f' {org}:',fs=9,c='#80CBC4',bold=True,ha='left') txt(ax,ex+3.8,ey,assoc,fs=9,c=WHITE,ha='left') ey-=0.31 # ── PROPHYLAXIS ────────────────────────────────────────────────── rbox(ax,0.3,3.65,23.4,1.72,'#151500',AMBER,lw=2,rad=0.2) rbox(ax,0.3,5.05,23.4,0.38,AMBER,AMBER,lw=0,rad=0.1) txt(ax,12,5.24,'PROPHYLAXIS — Pre/Intraoperative (Cataract Surgery)',fs=11,c=WHITE,bold=True) proph=[ ('GOLD STANDARD',GREEN,'5% Povidone-iodine into conjunctival fornix — leave for minimum 3 minutes before surgery'), ('INTRACAMERAL',GREEN,'Cefuroxime 1 mg / 0.1 ml injected into AC at END of surgery (standard of care)'), ('ALTERNATIVE',TEAL,'Moxifloxacin 0.5 mg / 0.1 ml intracameral (if cefuroxime not available)'), ('AVOID',RED,'Intracameral VANCOMYCIN as routine prophylaxis --> haemorrhagic occlusive retinal vasculitis'), ('NO EVIDENCE',ORANGE,'Preoperative topical fluoroquinolones — commonly used but evidence for efficacy is lacking'), ] px=0.6; py=4.88 for label,col,detail in proph: rbox(ax,px,py-0.17,2.5,0.33,col,col,lw=0,rad=0.08,alpha=0.25,zorder=2) txt(ax,px+1.25,py,label,fs=8.5,c=col,bold=True) txt(ax,px+2.8,py,detail,fs=8.8,c=WHITE,ha='left') py-=0.33 # ── MCQ STRIP ──────────────────────────────────────────────────── rbox(ax,0.3,1.3,23.4,2.25,'#1A0010',RED,lw=2,rad=0.2) rbox(ax,0.3,3.32,23.4,0.38,RED,RED,lw=0,rad=0.1) txt(ax,12,3.51,'HIGH-YIELD PG MCQ POINTS',fs=11,c='#EF9A9A',bold=True) mcqs=[ ('Most common organism post-cataract','S. epidermidis'), ('Gram +ve : Gram -ve ratio','90% : 10%'), ('EVS — immediate vitrectomy when','VA = Light Perception (LP)'), ('EVS — antibiotics alone when','VA = HM or better'), ('When to start topical steroids','AFTER 48 hours (not before)'), ('IVT vancomycin dose','2 mg / 0.1 ml'), ('IVT ceftazidime dose','2 mg / 0.1 ml'), ('Vitreous tap site (pseudophakic)','3.5 mm from limbus'), ('Avoid intracameral (routine)','Vancomycin (retinal vasculitis)'), ('Post-traumatic classic organism','Bacillus cereus'), ('Chronic delayed organism','Propionibacterium acnes'), ('Endogenous + liver abscess','Klebsiella (East Asian)'), ] col1x=0.6; col2x=7.0; col3x=12.6; col4x=19.0; my=3.18 for i,(q,a) in enumerate(mcqs): row=i%6; half=i//6 cy=my-row*0.30 qx=col1x if half==0 else col3x ax2x=col2x if half==0 else col4x txt(ax,qx,cy,f'• {q}:',fs=8.2,c=SUB,ha='left') txt(ax,ax2x,cy,a,fs=8.2,c=AMBER,bold=True,ha='left') txt(ax,12,0.52,"Source: Kanski's Clinical Ophthalmology 10th Ed. pp. 343-347 | Endophthalmitis Vitrectomy Study (EVS, 1995) | Wills Eye Manual", fs=8.5,c=SUB) plt.savefig('/tmp/workspace/ffa-pdf/Endophthalmitis_Decision_Tree.png', dpi=150,bbox_inches='tight',facecolor=BG) plt.close() print("Done:", plt.rcParams['figure.figsize']) PYEOF
ls -lh /tmp/workspace/ffa-pdf/Endophthalmitis_Decision_Tree.png
Endophthalmitis Decision Tree
Image · PNG
| Section | Content |
|---|---|
| Clinical suspicion | Trigger signs: pain + redness + visual loss + hypopyon + vitritis + loss of red reflex |
| Step 1 — Immediate actions | B-scan US · Aqueous tap (0.1-0.2 ml, 25G, limbal) · Vitreous tap (0.2-0.4 ml, 23G, 3.5 mm from limbus) · Send to microbiology immediately |
| Step 2 — IVT antibiotics | Vancomycin 2 mg/0.1 ml + Ceftazidime 2 mg/0.1 ml · Amikacin alternative + retinal toxicity warning |
| EVS diamond | Key decision point: VA = LP → left branch; VA = HM or better → right branch |
| Left branch (RED) | Immediate PPV — 3× better outcome; EVS landmark result |
| Right branch (GREEN) | Antibiotics alone — vitrectomy not superior at HM level |
| Step 3 — 48 hours | Repeat IVT abx · Start topical steroids AFTER 48 hrs · Oral fluoroquinolones × 10-14 days · Escalate criteria |
| Classification panels | Acute post-op / Chronic-delayed (P. acnes) / Post-traumatic (B. cereus) — each with organisms, onset, Rx |
| Endogenous row | Candida / Aspergillus / Klebsiella (liver abscess, East Asian) / S. aureus |
| Prophylaxis strip | Povidone-iodine (gold standard) · Intracameral cefuroxime · Avoid vancomycin (retinal vasculitis) |
| MCQ points (12) | All high-yield answers in amber |
Create a visual timeline/decision tree for endophthalmitis management (EVS criteria) Clean white page
python3 << 'PYEOF' import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt import matplotlib.patches as mpatches from matplotlib.patches import FancyBboxPatch, FancyArrowPatch import numpy as np fig = plt.figure(figsize=(22, 30), facecolor='white') ax = fig.add_axes([0, 0, 1, 1]) ax.set_xlim(0, 22); ax.set_ylim(0, 30); ax.axis('off') ax.set_facecolor('white') # ── Palette (light theme) ───────────────────────────────────────── WHITE = '#FFFFFF' OFFWHT = '#F8FAFC' BLACK = '#1A2233' DKGREY = '#374151' GREY = '#6B7280' LGREY = '#E5E7EB' XLGREY = '#F3F4F6' RED = '#DC2626'; RED_L = '#FEF2F2'; RED_M = '#FCA5A5' ORANGE = '#EA580C'; ORG_L = '#FFF7ED'; ORG_M = '#FDBA74' GREEN = '#16A34A'; GRN_L = '#F0FDF4'; GRN_M = '#86EFAC' BLUE = '#1D4ED8'; BLU_L = '#EFF6FF'; BLU_M = '#93C5FD' TEAL = '#0D9488'; TEL_L = '#F0FDFA'; TEL_M = '#5EEAD4' PURPLE = '#7C3AED'; PUR_L = '#F5F3FF'; PUR_M = '#C4B5FD' AMBER = '#D97706'; AMB_L = '#FFFBEB'; AMB_M = '#FCD34D' INDIGO = '#4338CA' def rbox(ax, x, y, w, h, fc, ec, lw=1.5, rad=0.15, zorder=3, alpha=1.0): p = FancyBboxPatch((x,y), w, h, boxstyle=f"round,pad={rad}", facecolor=fc, edgecolor=ec, linewidth=lw, zorder=zorder, alpha=alpha) ax.add_patch(p) def txt(ax, x, y, t, fs=10, c=BLACK, bold=False, ha='center', va='center', zorder=8): ax.text(x, y, t, fontsize=fs, color=c, fontweight='bold' if bold else 'normal', ha=ha, va=va, zorder=zorder, multialignment='center' if ha=='center' else 'left') def arrow_down(ax, x, y1, y2, c=GREY, lw=2, head=12): ax.annotate('', xy=(x, y2), xytext=(x, y1), arrowprops=dict(arrowstyle='->', color=c, lw=lw, mutation_scale=head), zorder=6) def arrow_h(ax, x1, x2, y, c=GREY, lw=2, head=12): ax.annotate('', xy=(x2, y), xytext=(x1, y), arrowprops=dict(arrowstyle='->', color=c, lw=lw, mutation_scale=head), zorder=6) def diamond(ax, cx, cy, w, h, fc, ec, lw=2, zorder=4): dx=w/2; dy=h/2 pts = np.array([[cx, cy+dy],[cx+dx, cy],[cx, cy-dy],[cx-dx, cy]]) poly = plt.Polygon(pts, facecolor=fc, edgecolor=ec, linewidth=lw, zorder=zorder) ax.add_patch(poly) def step_circle(ax, x, y, n, fc, ec): c = plt.Circle((x, y), 0.32, facecolor=fc, edgecolor=ec, lw=2, zorder=8) ax.add_patch(c) txt(ax, x, y, str(n), fs=11, c=WHITE, bold=True, zorder=9) def hline(ax, x1, x2, y, c=LGREY, lw=1): ax.plot([x1, x2], [y, y], color=c, lw=lw, zorder=2) # ══════════════════════════════════════════════════════════════════ # TITLE # ══════════════════════════════════════════════════════════════════ rbox(ax, 0.4, 28.5, 21.2, 1.25, BLU_L, BLUE, lw=2.5, rad=0.2) txt(ax, 11, 29.28, 'ENDOPHTHALMITIS | Management Decision Tree', fs=18, c=BLUE, bold=True) txt(ax, 11, 28.78, 'EVS Criteria · Treatment Protocol · Classification | Kanski 10th Ed. pp. 343–347', fs=10, c=GREY) # ══════════════════════════════════════════════════════════════════ # TRIGGER BOX # ══════════════════════════════════════════════════════════════════ rbox(ax, 4.5, 27.0, 13.0, 1.25, XLGREY, DKGREY, lw=1.5, rad=0.18) txt(ax, 11, 27.75, 'CLINICAL SUSPICION', fs=13, c=BLACK, bold=True) txt(ax, 11, 27.3, 'Pain · Redness · Visual loss · Hypopyon · Vitritis · Loss of red reflex', fs=9.5, c=DKGREY) arrow_down(ax, 11, 27.0, 26.4, c=DKGREY, lw=2, head=12) # ══════════════════════════════════════════════════════════════════ # STEP 1 # ══════════════════════════════════════════════════════════════════ step_circle(ax, 1.15, 26.0, 1, ORANGE, '#C2410C') rbox(ax, 1.6, 25.18, 18.8, 1.55, ORG_L, ORANGE, lw=2, rad=0.18) txt(ax, 11, 26.4, 'STEP 1 — IMMEDIATE ACTIONS (Do Not Delay)', fs=12, c=ORANGE, bold=True) txt(ax, 11, 25.98, 'B-scan ultrasound if no view of fundus | Aqueous tap: 0.1–0.2 ml via limbal paracentesis, 25G needle', fs=9.2, c=DKGREY) txt(ax, 11, 25.58, 'Vitreous tap: 0.2–0.4 ml via vitrector or 23G needle · Enter 3.5 mm from limbus (pseudophakic)', fs=9.2, c=DKGREY) txt(ax, 11, 25.22, 'Send specimens to microbiology IMMEDIATELY · Then give intravitreal antibiotics without further delay', fs=9, c=ORANGE, bold=True) arrow_down(ax, 11, 25.18, 24.58, c=ORANGE, lw=2, head=12) # ══════════════════════════════════════════════════════════════════ # STEP 2 — IVT ANTIBIOTICS # ══════════════════════════════════════════════════════════════════ step_circle(ax, 1.15, 24.15, 2, GREEN, '#15803D') rbox(ax, 1.6, 23.25, 18.8, 1.65, GRN_L, GREEN, lw=2, rad=0.18) txt(ax, 11, 24.6, 'STEP 2 — INTRAVITREAL ANTIBIOTICS (Always — given immediately after cultures)', fs=12, c=GREEN, bold=True) # Two drug boxes rbox(ax, 2.2, 23.42, 7.8, 0.72, WHITE, GREEN, lw=1.5, rad=0.1) txt(ax, 6.1, 23.78, 'VANCOMYCIN 2 mg / 0.1 ml', fs=10, c=GREEN, bold=True) txt(ax, 6.1, 23.52, 'Gram-positive organisms (incl. MRSA)', fs=9, c=DKGREY) rbox(ax, 11.2, 23.42, 8.4, 0.72, WHITE, GREEN, lw=1.5, rad=0.1) txt(ax, 15.4, 23.78, 'CEFTAZIDIME 2 mg / 0.1 ml', fs=10, c=GREEN, bold=True) txt(ax, 15.4, 23.52, 'Gram-negative organisms (incl. Pseudomonas)', fs=9, c=DKGREY) txt(ax, 11, 23.35, 'Alternative: Amikacin 0.4 mg/0.1 ml (only if true penicillin allergy — more retinal toxic)', fs=8.5, c=GREY) arrow_down(ax, 11, 23.25, 22.65, c=GREEN, lw=2, head=12) # ══════════════════════════════════════════════════════════════════ # EVS DIAMOND # ══════════════════════════════════════════════════════════════════ diamond(ax, 11, 21.55, 7.2, 2.0, BLU_L, BLUE, lw=2.5, zorder=4) txt(ax, 11, 21.88, 'EVS KEY DECISION', fs=12, c=BLUE, bold=True, zorder=8) txt(ax, 11, 21.45, 'What is the presenting', fs=9.5, c=DKGREY, zorder=8) txt(ax, 11, 21.18, 'visual acuity?', fs=9.5, c=DKGREY, zorder=8) # Side labels above arrows txt(ax, 4.0, 22.0, 'VA = LIGHT PERCEPTION', fs=10.5, c=RED, bold=True, ha='center') txt(ax, 4.0, 21.68, '(LP or worse)', fs=9, c=RED, ha='center') txt(ax, 18.0, 22.0, 'VA = HAND MOVEMENTS', fs=10.5, c=GREEN, bold=True, ha='center') txt(ax, 18.0, 21.68, '(HM or better)', fs=9, c=GREEN, ha='center') # Horizontal arrows from diamond arrow_h(ax, 7.4, 5.2, 21.55, c=RED, lw=2.5, head=14) arrow_h(ax, 14.6, 16.8, 21.55, c=GREEN, lw=2.5, head=14) # ══════════════════════════════════════════════════════════════════ # LEFT — IMMEDIATE PPV # ══════════════════════════════════════════════════════════════════ rbox(ax, 0.4, 18.55, 9.2, 2.85, RED_L, RED, lw=2.5, rad=0.2) rbox(ax, 0.4, 21.1, 9.2, 0.38, RED, RED, lw=0, rad=0.1, alpha=1.0) txt(ax, 5.0, 21.28, 'IMMEDIATE PPV', fs=12, c=WHITE, bold=True) evs_items_l = [ ('VA = Light Perception', RED, True), ('Immediate Pars Plana Vitrectomy', BLACK, True), ('EVS: 3x better visual outcome', DKGREY, False), ('vs tap-and-inject alone', DKGREY, False), ('Removes bacterial load + toxins', DKGREY, False), ('Better antibiotic penetration post-PPV', DKGREY, False), ('Repeat IVT antibiotics intraoperatively', DKGREY, False), ] ly = 20.85 for t_str, col, bold in evs_items_l: txt(ax, 5.0, ly, t_str, fs=9, c=col, bold=bold) ly -= 0.34 # ══════════════════════════════════════════════════════════════════ # RIGHT — ANTIBIOTICS ALONE # ══════════════════════════════════════════════════════════════════ rbox(ax, 12.4, 18.55, 9.2, 2.85, GRN_L, GREEN, lw=2.5, rad=0.2) rbox(ax, 12.4, 21.1, 9.2, 0.38, GREEN, GREEN, lw=0, rad=0.1, alpha=1.0) txt(ax, 17.0, 21.28, 'INTRAVITREAL ANTIBIOTICS ALONE', fs=11, c=WHITE, bold=True) evs_items_r = [ ('VA = Hand Movements or better', GREEN, True), ('Tap + Inject only (no vitrectomy)', BLACK, True), ('EVS: vitrectomy NOT superior at HM', DKGREY, False), ('Intravitreal vancomycin + ceftazidime', DKGREY, False), ('Monitor closely — review at 36-48 hrs', DKGREY, False), ('Escalate to PPV if no improvement', DKGREY, False), ('', DKGREY, False), ] ry = 20.85 for t_str, col, bold in evs_items_r: txt(ax, 17.0, ry, t_str, fs=9, c=col, bold=bold) ry -= 0.34 # ── Merge arrows down to Step 3 ────────────────────────────────── arrow_down(ax, 5.0, 18.55, 18.0, c=RED, lw=1.8, head=10) arrow_down(ax, 17.0, 18.55, 18.0, c=GREEN, lw=1.8, head=10) ax.plot([5.0, 5.0],[18.0, 18.0], color=RED, lw=1.5, zorder=5) ax.plot([17.0,17.0],[18.0,18.0], color=GREEN, lw=1.5, zorder=5) ax.plot([5.0, 17.0],[18.0, 18.0], color=LGREY, lw=1.5, zorder=5) arrow_down(ax, 11, 18.0, 17.4, c=DKGREY, lw=2, head=12) # ══════════════════════════════════════════════════════════════════ # STEP 3 — 48 HOUR REASSESSMENT # ══════════════════════════════════════════════════════════════════ step_circle(ax, 1.15, 17.0, 3, BLUE, '#1E40AF') rbox(ax, 1.6, 16.15, 18.8, 1.6, BLU_L, BLUE, lw=2, rad=0.18) txt(ax, 11, 17.38, 'STEP 3 — REASSESS AT 48 HOURS', fs=12, c=BLUE, bold=True) txt(ax, 11, 16.98, 'Repeat intravitreal antibiotics | Start TOPICAL STEROIDS (begin AFTER 48 hrs — not before) | Add CYCLOPLEGIC agent', fs=9.2, c=DKGREY) txt(ax, 11, 16.6, 'Oral fluoroquinolones x 10–14 days | Consider PPV if: no improvement / worsening / fistula-related / virulent organism suspected', fs=9, c=DKGREY) txt(ax, 11, 16.28, 'Negative culture does NOT rule out infection — continue treatment', fs=8.8, c=ORANGE, bold=True) arrow_down(ax, 11, 16.15, 15.55, c=DKGREY, lw=1.5, head=10) # ══════════════════════════════════════════════════════════════════ # CLASSIFICATION — 3 COLUMNS # ══════════════════════════════════════════════════════════════════ rbox(ax, 0.4, 15.08, 21.2, 0.42, XLGREY, LGREY, lw=1, rad=0.1) txt(ax, 11, 15.29, 'CLASSIFICATION BY TYPE', fs=11, c=DKGREY, bold=True) cls_data = [ (0.4, 'ACUTE POST-OP', RED, [('Onset', 'Days 2–7 post cataract surgery'), ('Organisms', 'S. epidermidis (most common)'), ('', 'S. aureus · Streptococcus spp.'), ('Gram', '~90% Gram-positive'), ('EVS', 'Applies to this type'), ('Prognosis', 'Good with early treatment')]), (7.7, 'CHRONIC / DELAYED', PURPLE, [('Onset', 'Weeks–months post-op'), ('Organism', 'Propionibacterium acnes'), ('Sign', 'White intracapsular plaque'), ('Diagnosis', 'PCR + capsule culture'), ('Rx', 'Vitrectomy + TOTAL capsulectomy'), ('', '+ IVT vancomycin')]), (15.0, 'POST-TRAUMATIC', ORANGE, [('Classic', 'Bacillus cereus (soil/vegetable)'), ('Prognosis', 'VERY POOR — may need evisceration'), ('Others', 'S. aureus · Fungi'), ('Onset', '24–48 hrs post injury'), ('Rx', 'Emergency vitrectomy'), ('', '+ broad-spectrum IVT abx')]), ] for bx, title, col, items in cls_data: rbox(ax, bx, 11.8, 6.9, 3.1, WHITE, col, lw=2, rad=0.18) rbox(ax, bx, 14.7, 6.9, 0.38, col, col, lw=0, rad=0.1) txt(ax, bx+3.45, 14.89, title, fs=10.5, c=WHITE, bold=True) iy = 14.52 for k, v in items: if k: txt(ax, bx+0.25, iy, f'{k}:', fs=8.8, c=col, bold=True, ha='left') txt(ax, bx+2.35, iy, v, fs=8.8, c=BLACK, ha='left') else: txt(ax, bx+2.35, iy, v, fs=8.8, c=GREY, ha='left') iy -= 0.44 # ENDOGENOUS row rbox(ax, 0.4, 10.35, 21.2, 1.28, TEL_L, TEAL, lw=1.8, rad=0.18) rbox(ax, 0.4, 11.38, 21.2, 0.38, TEAL, TEAL, lw=0, rad=0.1) txt(ax, 11, 11.57, 'ENDOGENOUS ENDOPHTHALMITIS (Haematogenous — no surgery/trauma)', fs=11, c=WHITE, bold=True) endo = [ ('Candida', 'IV drug use · prolonged IV catheters · immunosuppressed · bilateral ~25%'), ('Aspergillus', 'Neutropenia · bone marrow transplant · haematological malignancy'), ('Klebsiella', 'Liver abscess --> EAST ASIAN PATIENTS (classic PG MCQ)'), ('S. aureus', 'Septicaemia · IV drug use · diabetes · renal failure'), ] ex = 0.7; ey = 11.18 for org, assoc in endo: txt(ax, ex, ey, f'{org}:', fs=9, c=TEAL, bold=True, ha='left') txt(ax, ex+2.4, ey, assoc, fs=9, c=DKGREY, ha='left') ey -= 0.27 # ══════════════════════════════════════════════════════════════════ # PROPHYLAXIS # ══════════════════════════════════════════════════════════════════ rbox(ax, 0.4, 8.42, 21.2, 1.76, AMB_L, AMBER, lw=2, rad=0.18) rbox(ax, 0.4, 9.92, 21.2, 0.38, AMBER, AMBER, lw=0, rad=0.1) txt(ax, 11, 10.11, 'PROPHYLAXIS — Pre/Intraoperative', fs=11, c=WHITE, bold=True) proph = [ (GREEN, 'GOLD STANDARD', '5% Povidone-iodine into conjunctival fornix — leave for minimum 3 minutes before incision'), (GREEN, 'INTRACAMERAL', 'Cefuroxime 1 mg / 0.1 ml injected into AC at END of surgery (standard of care)'), (TEAL, 'ALTERNATIVE', 'Moxifloxacin 0.5 mg / 0.1 ml intracameral (if cefuroxime not available)'), (RED, 'AVOID', 'Intracameral VANCOMYCIN as routine prophylaxis --> haemorrhagic occlusive retinal vasculitis'), (AMBER, 'NO CLEAR EVIDENCE','Preop topical fluoroquinolones — commonly used; evidence for efficacy is lacking'), ] px = 0.7; py = 9.7 for col, label, detail in proph: rbox(ax, px, py-0.18, 2.8, 0.32, col, col, lw=0, rad=0.06, alpha=0.15, zorder=2) txt(ax, px+1.4, py, label, fs=8.5, c=col, bold=True, ha='center') txt(ax, px+3.2, py, detail, fs=8.8, c=DKGREY, ha='left') py -= 0.32 # ══════════════════════════════════════════════════════════════════ # MCQ BOX # ══════════════════════════════════════════════════════════════════ rbox(ax, 0.4, 1.55, 21.2, 6.7, XLGREY, LGREY, lw=1.5, rad=0.2) rbox(ax, 0.4, 7.88, 21.2, 0.38, DKGREY, DKGREY, lw=0, rad=0.1) txt(ax, 11, 8.07, 'HIGH-YIELD PG MCQ POINTS', fs=11, c=WHITE, bold=True) hline(ax, 0.6, 21.4, 7.58, c=LGREY) hline(ax, 0.6, 21.4, 7.28, c=LGREY) hline(ax, 0.6, 21.4, 6.98, c=LGREY) hline(ax, 0.6, 21.4, 6.68, c=LGREY) hline(ax, 0.6, 21.4, 6.38, c=LGREY) hline(ax, 0.6, 21.4, 6.08, c=LGREY) ax.plot([11.2, 11.2], [1.7, 7.82], color=LGREY, lw=1, zorder=2) mcqs = [ ('Most common organism post-cataract', 'S. epidermidis'), ('Gram +ve : Gram -ve ratio', '90% : 10%'), ('EVS — immediate vitrectomy when', 'VA = Light Perception (LP)'), ('EVS — antibiotics alone when', 'VA = HM or better'), ('When to start topical steroids', 'AFTER 48 hours (not before)'), ('Oral antibiotic duration', '10–14 days fluoroquinolones'), ('IVT vancomycin dose', '2 mg / 0.1 ml'), ('IVT ceftazidime dose', '2 mg / 0.1 ml'), ('Vitreous tap site (pseudophakic)', '3.5 mm from limbus'), ('Avoid as intracameral prophylaxis', 'Vancomycin (retinal vasculitis)'), ('Post-traumatic classic organism', 'Bacillus cereus'), ('Chronic/delayed organism', 'Propionibacterium acnes (capsular plaque)'), ] my = 7.62 for i, (q, a) in enumerate(mcqs): col = 0 if i < 6 else 1 row = i % 6 qx = 0.75 if col == 0 else 11.5 ax_x = 7.0 if col == 0 else 17.5 cy = my - row * 0.30 txt(ax, qx, cy, f' {q}', fs=8.8, c=DKGREY, ha='left') txt(ax, ax_x, cy, a, fs=8.8, c=BLUE, bold=True, ha='left') # Column headers txt(ax, 3.9, 7.78, 'MCQ POINT', fs=8, c=GREY, bold=True) txt(ax, 7.0, 7.78, 'ANSWER', fs=8, c=GREY, bold=True, ha='left') txt(ax, 14.6, 7.78, 'MCQ POINT', fs=8, c=GREY, bold=True) txt(ax, 17.5, 7.78, 'ANSWER', fs=8, c=GREY, bold=True, ha='left') # ══════════════════════════════════════════════════════════════════ # FOOTER # ══════════════════════════════════════════════════════════════════ txt(ax, 11, 0.75, "Kanski's Clinical Ophthalmology 10th Ed. pp. 343–347 | Endophthalmitis Vitrectomy Study (EVS, 1995) | Wills Eye Manual", fs=9, c=GREY) hline(ax, 0.4, 21.6, 1.05, c=LGREY, lw=1.5) plt.savefig('/tmp/workspace/ffa-pdf/Endophthalmitis_Decision_Tree_Light.png', dpi=150, bbox_inches='tight', facecolor='white') plt.close() print("Done") PYEOF
ls -lh /tmp/workspace/ffa-pdf/Endophthalmitis_Decision_Tree_Light.png
Endophthalmitis Decision Tree Light
Image · PNG