Mucosal folds and middle ear ventilation

| Fold | Role |
|---|---|
| Tensor fold (anterior malleolar fold) | Anterior boundary of the epitympanum diaphragm |
| Interosseous fold (superior incudal fold) | Connects malleus head to incus body |
| Medial incudal fold | Closes the medial aspect of the attic |
"The connection between the two [malleal folds] is the tympanic isthmus, which is the only conduit for a ventilation stream in the epitympanum from the Eustachian tube, unlike the mesotympanum, which is flooded by air at all times."
- Scott-Brown's Otorhinolaryngology Head & Neck Surgery, Vol 2
Create a visual reference guide on mucosal fold anatomy and ventilation pathways
mkdir -p /home/daytona/workspace/middle-ear-guide && ls /home/daytona/workspace/…
~/middle-ear-guide/build_guide.py
"""
Visual Reference Guide: Mucosal Fold Anatomy and Middle Ear Ventilation Pathways
Built with ReportLab - produces a multi-page, diagram-rich PDF
"""
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import mm, cm
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, KeepTogether, PageBreak
)
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.graphics.shapes import (
Drawing, Rect, Ellipse, Line, Circle, Polygon, String,
Path, Group, PolyLine
)
from reportlab.graphics import renderPDF
from reportlab.graphics.shapes import ArcPath
import math
# ── Colour palette ──────────────────────────────────────────────────────────
TEAL = colors.HexColor("#0D6E8A")
TEAL_LIGHT = colors.HexColor("#E0F4F8")
TEAL_MID = colors.HexColor("#5CB8D0")
AMBER = colors.HexColor("#E8A020")
AMBER_LIGHT= colors.HexColor("#FFF3DC")
RED = colors.HexColor("#C0392B")
RED_LIGHT = colors.HexColor("#FDECEA")
GREEN = colors.HexColor("#1A7A4A")
GREEN_LIGHT= colors.HexColor("#E8F8EE")
PURPLE = colors.HexColor("#6B3FA0")
PURPLE_LIGHT=colors.HexColor("#F2EBF9")
BONE = colors.HexColor("#F5EFE0")
BONE_DARK = colors.HexColor("#D4C9A8")
MUCOSA = colors.HexColor("#F0A090")
MUCOSA_LIGHT=colors.HexColor("#FAE0DA")
DARK = colors.HexColor("#1A2535")
GRAY_LIGHT = colors.HexColor("#F4F6F8")
GRAY_MID = colors.HexColor("#BDC3CC")
W, H = A4 # 595 x 842 pts
# ── Styles ───────────────────────────────────────────────────────────────────
base = getSampleStyleSheet()
def make_style(name, parent="Normal", **kw):
return ParagraphStyle(name, parent=base[parent], **kw)
TITLE_STYLE = make_style("Title2", "Normal",
fontSize=26, fontName="Helvetica-Bold", textColor=DARK,
alignment=TA_CENTER, spaceAfter=4)
SUBTITLE_STYLE = make_style("Sub", "Normal",
fontSize=13, fontName="Helvetica", textColor=TEAL,
alignment=TA_CENTER, spaceAfter=2)
CREDIT_STYLE = make_style("Credit", "Normal",
fontSize=8, fontName="Helvetica", textColor=GRAY_MID,
alignment=TA_CENTER)
H1_STYLE = make_style("H1", "Normal",
fontSize=15, fontName="Helvetica-Bold", textColor=colors.white,
spaceBefore=0, spaceAfter=0, leftIndent=8)
H2_STYLE = make_style("H2", "Normal",
fontSize=12, fontName="Helvetica-Bold", textColor=TEAL,
spaceBefore=10, spaceAfter=4)
BODY_STYLE = make_style("Body2", "Normal",
fontSize=9.5, fontName="Helvetica", textColor=DARK,
spaceAfter=5, leading=14, alignment=TA_JUSTIFY)
BOLD_STYLE = make_style("Bold2", "Normal",
fontSize=9.5, fontName="Helvetica-Bold", textColor=DARK,
spaceAfter=5, leading=14)
LABEL_STYLE = make_style("Label", "Normal",
fontSize=8, fontName="Helvetica", textColor=DARK)
CAPTION_STYLE= make_style("Caption", "Normal",
fontSize=8, fontName="Helvetica-Oblique", textColor=colors.HexColor("#555555"),
alignment=TA_CENTER, spaceBefore=2, spaceAfter=6)
KEY_STYLE = make_style("Key", "Normal",
fontSize=8.5, fontName="Helvetica", textColor=DARK, leading=13)
BOX_STYLE = make_style("Box", "Normal",
fontSize=9, fontName="Helvetica", textColor=DARK,
leading=14, leftIndent=4, alignment=TA_LEFT)
SMALL_STYLE = make_style("Small", "Normal",
fontSize=8, fontName="Helvetica", textColor=DARK, leading=12)
# ── Helper: coloured header banner ───────────────────────────────────────────
def section_banner(text, color=TEAL, w=None):
if w is None:
w = W - 40*mm
d = Drawing(w, 22)
d.add(Rect(0, 0, w, 22, fillColor=color, strokeColor=None))
d.add(String(8, 6, text,
fontName="Helvetica-Bold", fontSize=12,
fillColor=colors.white))
return d
# ── Helper: small colour swatch legend row ───────────────────────────────────
def swatch_row(items):
"""items = list of (color, label)"""
parts = []
for col, lbl in items:
d = Drawing(14, 10)
d.add(Rect(0, 1, 12, 9, fillColor=col, strokeColor=colors.HexColor("#888888"), strokeWidth=0.5))
parts.append(d)
parts.append(Paragraph(f"<b>{lbl}</b> ", KEY_STYLE))
return parts
# ═══════════════════════════════════════════════════════════════════════════
# PAGE 1 DIAGRAM – Coronal cross-section of the middle ear
# ═══════════════════════════════════════════════════════════════════════════
def draw_coronal_section():
"""
Schematic coronal cross-section showing:
- Epitympanum (attic) vs. mesotympanum
- Malleus, incus, stapes
- Key mucosal folds
- Isthmus anticus and posticus
- Prussak's space
- Eustachian tube opening
- Ventilation arrows
"""
DW, DH = 480, 370
d = Drawing(DW, DH)
# ── Background bony walls ───────────────────────────────────────────────
# Outer bony shell
d.add(Rect(10, 10, DW-20, DH-20,
fillColor=BONE, strokeColor=BONE_DARK, strokeWidth=2, rx=8, ry=8))
# ── Middle ear cavity ──────────────────────────────────────────────────
# Mesotympanum (lower, freely ventilated)
d.add(Rect(60, 55, 360, 130,
fillColor=colors.HexColor("#D6EEF7"),
strokeColor=TEAL_MID, strokeWidth=1.2))
# Epitympanum / attic (upper, only via isthmus)
d.add(Rect(60, 185, 360, 130,
fillColor=colors.HexColor("#C8E6C9"),
strokeColor=GREEN, strokeWidth=1.2))
# Labels for spaces
d.add(String(65, 125, "MESOTYMPANUM", fontName="Helvetica-Bold",
fontSize=9, fillColor=TEAL))
d.add(String(65, 112, "(freely ventilated via ET)", fontName="Helvetica",
fontSize=7.5, fillColor=TEAL))
d.add(String(65, 295, "EPITYMPANUM / ATTIC", fontName="Helvetica-Bold",
fontSize=9, fillColor=GREEN))
d.add(String(65, 282, "(ventilated only via isthmus)", fontName="Helvetica",
fontSize=7.5, fillColor=GREEN))
# ── Eustachian tube (bottom-left) ──────────────────────────────────────
et_x, et_y = 60, 55
d.add(Rect(et_x-40, et_y-30, 60, 28,
fillColor=colors.HexColor("#BFD8F0"),
strokeColor=TEAL, strokeWidth=1.2))
d.add(String(et_x-38, et_y-20, "Eustachian", fontName="Helvetica-Bold",
fontSize=7, fillColor=TEAL))
d.add(String(et_x-35, et_y-30, "Tube", fontName="Helvetica-Bold",
fontSize=7, fillColor=TEAL))
# ET arrow into mesotympanum
d.add(Line(et_x+18, et_y-16, et_x+60, et_y+20,
strokeColor=TEAL, strokeWidth=1.5))
d.add(Polygon([et_x+60, et_y+20, et_x+54, et_y+14, et_x+66, et_y+14],
fillColor=TEAL, strokeColor=TEAL))
# ── OSSICLES ────────────────────────────────────────────────────────────
# Malleus (handle descends, head in attic)
# Head of malleus (attic)
d.add(Ellipse(230, 270, 28, 22,
fillColor=BONE_DARK, strokeColor=DARK, strokeWidth=1.5))
d.add(String(218, 262, "Head of", fontName="Helvetica", fontSize=7, fillColor=DARK))
d.add(String(216, 253, "Malleus", fontName="Helvetica-Bold", fontSize=7, fillColor=DARK))
# Neck of malleus
d.add(Rect(225, 180, 10, 80, fillColor=BONE_DARK, strokeColor=DARK, strokeWidth=1))
# Handle of malleus (mesotympanum, angles laterally)
d.add(Line(230, 180, 215, 90, strokeColor=BONE_DARK, strokeWidth=8))
d.add(Line(230, 180, 215, 90, strokeColor=DARK, strokeWidth=1.2))
d.add(String(175, 82, "Handle of Malleus", fontName="Helvetica", fontSize=7, fillColor=DARK))
# Incus body (attic, articulates with malleus head)
d.add(Ellipse(295, 265, 25, 18,
fillColor=BONE_DARK, strokeColor=DARK, strokeWidth=1.5))
d.add(String(285, 258, "Body of", fontName="Helvetica", fontSize=7, fillColor=DARK))
d.add(String(285, 249, "Incus", fontName="Helvetica-Bold", fontSize=7, fillColor=DARK))
# Long process of incus (descending into mesotympanum)
d.add(Line(290, 248, 290, 145, strokeColor=BONE_DARK, strokeWidth=7))
d.add(Line(290, 248, 290, 145, strokeColor=DARK, strokeWidth=1.2))
# Stapes (mesotympanum, head at bottom of incus long process)
d.add(Ellipse(290, 138, 18, 10,
fillColor=BONE_DARK, strokeColor=DARK, strokeWidth=1.2))
d.add(String(297, 133, "Stapes", fontName="Helvetica-Bold", fontSize=7, fillColor=DARK))
# Crura
d.add(Line(278, 133, 268, 108, strokeColor=BONE_DARK, strokeWidth=4))
d.add(Line(278, 133, 268, 108, strokeColor=DARK, strokeWidth=0.8))
d.add(Line(302, 133, 312, 108, strokeColor=BONE_DARK, strokeWidth=4))
d.add(Line(302, 133, 312, 108, strokeColor=DARK, strokeWidth=0.8))
d.add(Line(268, 108, 312, 108, strokeColor=BONE_DARK, strokeWidth=5))
d.add(Line(268, 108, 312, 108, strokeColor=DARK, strokeWidth=1))
d.add(String(315, 104, "Footplate", fontName="Helvetica", fontSize=7, fillColor=DARK))
# ── MUCOSAL FOLDS ───────────────────────────────────────────────────────
# Tensor fold (anterior - separates attic from meso anteriorly)
fold_y = 185 # diaphragm level
d.add(Line(60, fold_y, 220, fold_y, # anterior tensor fold
strokeColor=MUCOSA, strokeWidth=3))
d.add(Line(245, fold_y, 285, fold_y, # gap = isthmus anticus
strokeColor=colors.HexColor("#AAAAAA"), strokeWidth=0.5,
strokeDashArray=[3,3]))
d.add(Line(295, fold_y, 360, fold_y, # interosseous / medial incudal fold
strokeColor=MUCOSA, strokeWidth=3))
d.add(Line(360, fold_y, 420, fold_y, # posterior fold
strokeColor=MUCOSA, strokeWidth=3))
# Second gap = isthmus posticus (posterior to stapes)
# already shown implicitly between 295-360 band
# Lateral malleolar fold (lateral wall of Prussak's space)
d.add(Line(220, fold_y, 220, 315, strokeColor=RED, strokeWidth=2))
d.add(String(140, 320, "Lat. Malleolar", fontName="Helvetica", fontSize=7.5, fillColor=RED))
d.add(String(140, 311, "Fold", fontName="Helvetica-Bold", fontSize=7.5, fillColor=RED))
# Superior malleolar fold (top, horizontal)
d.add(Line(200, 310, 250, 310, strokeColor=AMBER, strokeWidth=2))
d.add(String(155, 324, "Sup. Malleolar Fold", fontName="Helvetica", fontSize=7, fillColor=AMBER))
# Superior incudal fold (connects incus to tegmen)
d.add(Line(285, 295, 285, 315, strokeColor=PURPLE, strokeWidth=2))
d.add(String(325, 320, "Sup. Incudal Fold", fontName="Helvetica", fontSize=7, fillColor=PURPLE))
# Posterior incudal ligament (incus to fossa incudis)
d.add(Line(310, 255, 360, 260, strokeColor=PURPLE, strokeWidth=2,
strokeDashArray=[4,2]))
d.add(String(365, 258, "Post. Incudal Lig.", fontName="Helvetica", fontSize=7, fillColor=PURPLE))
# Anterior malleolar fold label
d.add(String(63, 192, "Anterior Malleolar / Tensor Fold", fontName="Helvetica-Bold",
fontSize=7.5, fillColor=MUCOSA))
# Medial incudal fold label
d.add(String(300, 192, "Medial Incudal Fold", fontName="Helvetica-Bold",
fontSize=7.5, fillColor=MUCOSA))
# Plica stapedis (around stapes)
d.add(Ellipse(290, 128, 28, 14, fillColor=None,
strokeColor=AMBER, strokeWidth=1.5, strokeDashArray=[3,2]))
d.add(String(318, 122, "Plica Stapedis", fontName="Helvetica", fontSize=7, fillColor=AMBER))
# ── ISTHMUS labels & arrows ──────────────────────────────────────────────
# Isthmus anticus (between tensor fold end and stapes)
d.add(Rect(220, 178, 28, 14,
fillColor=colors.HexColor("#FFE0B2"),
strokeColor=AMBER, strokeWidth=1.2))
d.add(String(222, 182, "Isthmus", fontName="Helvetica-Bold", fontSize=6.5, fillColor=AMBER))
d.add(String(222, 174, "Anticus", fontName="Helvetica-Bold", fontSize=6.5, fillColor=AMBER))
# Isthmus posticus (posterior to stapes footplate area)
d.add(Rect(358, 178, 28, 14,
fillColor=colors.HexColor("#FFE0B2"),
strokeColor=AMBER, strokeWidth=1.2))
d.add(String(360, 182, "Isthmus", fontName="Helvetica-Bold", fontSize=6.5, fillColor=AMBER))
d.add(String(360, 174, "Posticus", fontName="Helvetica-Bold", fontSize=6.5, fillColor=AMBER))
# ── PRUSSAK'S SPACE ──────────────────────────────────────────────────────
d.add(Rect(165, 190, 52, 110,
fillColor=colors.HexColor("#FFD6D6"),
strokeColor=RED, strokeWidth=1.5, strokeDashArray=[4,2]))
d.add(String(168, 280, "Prussak's", fontName="Helvetica-Bold", fontSize=8, fillColor=RED))
d.add(String(174, 270, "Space", fontName="Helvetica-Bold", fontSize=8, fillColor=RED))
# Pars flaccida (lateral wall of Prussak's)
d.add(Rect(145, 190, 18, 110,
fillColor=colors.HexColor("#FFB5B5"),
strokeColor=RED, strokeWidth=1.2))
d.add(String(108, 242, "Pars", fontName="Helvetica-Bold", fontSize=7, fillColor=RED))
d.add(String(103, 233, "Flaccida", fontName="Helvetica-Bold", fontSize=7, fillColor=RED))
# ── VENTILATION ARROWS ───────────────────────────────────────────────────
# Big blue arrow in mesotympanum = free air flow from ET
d.add(Line(110, 100, 380, 100, strokeColor=TEAL, strokeWidth=2))
d.add(Polygon([380, 100, 373, 95, 373, 105],
fillColor=TEAL, strokeColor=TEAL))
d.add(String(220, 90, "Free airflow in mesotympanum", fontName="Helvetica-Oblique",
fontSize=7.5, fillColor=TEAL))
# Upward arrows through isthmi into attic
# Through isthmus anticus
d.add(Line(234, 185, 234, 230, strokeColor=AMBER, strokeWidth=1.8))
d.add(Polygon([234, 230, 229, 222, 239, 222],
fillColor=AMBER, strokeColor=AMBER))
# Through isthmus posticus
d.add(Line(372, 185, 372, 230, strokeColor=AMBER, strokeWidth=1.8))
d.add(Polygon([372, 230, 367, 222, 377, 222],
fillColor=AMBER, strokeColor=AMBER))
# Blocked isthmus indicator (cholesteatoma scenario) - small X on anticus
# Not needed for base diagram
# ── TEGMEN (top bony wall) ────────────────────────────────────────────────
d.add(Rect(60, 315, 360, 20,
fillColor=BONE_DARK, strokeColor=DARK, strokeWidth=1))
d.add(String(215, 321, "TEGMEN", fontName="Helvetica-Bold", fontSize=8, fillColor=DARK))
# ── TYMPANIC MEMBRANE indicator ──────────────────────────────────────────
d.add(Line(60, 55, 60, 315, strokeColor=colors.HexColor("#8B6914"), strokeWidth=3))
d.add(String(20, 180, "TM", fontName="Helvetica-Bold", fontSize=8, fillColor=colors.HexColor("#8B6914")))
# ── PROMONTORY (medial wall) ──────────────────────────────────────────────
d.add(Rect(415, 70, 15, 120,
fillColor=BONE_DARK, strokeColor=DARK, strokeWidth=1))
d.add(String(430, 128, "Promontory", fontName="Helvetica", fontSize=7, fillColor=DARK))
# ── Oval window ──────────────────────────────────────────────────────────
d.add(Ellipse(420, 118, 10, 7,
fillColor=colors.HexColor("#D0E8F0"), strokeColor=DARK, strokeWidth=1))
d.add(String(430, 115, "Oval W.", fontName="Helvetica", fontSize=7, fillColor=DARK))
# Round window
d.add(Circle(420, 85, 6,
fillColor=colors.HexColor("#D0E8F0"), strokeColor=DARK, strokeWidth=1))
d.add(String(430, 82, "Round W.", fontName="Helvetica", fontSize=7, fillColor=DARK))
# ── Mucociliary pathways (dashed curved arrows) ──────────────────────────
# Promontorial pathway
d.add(Line(400, 80, 120, 80, strokeColor=colors.HexColor("#50A060"),
strokeWidth=1.2, strokeDashArray=[5,3]))
d.add(Polygon([120, 80, 128, 75, 128, 85],
fillColor=GREEN, strokeColor=GREEN))
d.add(String(220, 68, "Promontorial mucociliary pathway", fontName="Helvetica-Oblique",
fontSize=6.5, fillColor=GREEN))
# ── Border decoration ────────────────────────────────────────────────────
d.add(Rect(2, 2, DW-4, DH-4,
fillColor=None, strokeColor=TEAL_MID, strokeWidth=1.5, rx=6, ry=6))
return d
# ═══════════════════════════════════════════════════════════════════════════
# PAGE 2 DIAGRAM – Superior view (attic diaphragm)
# ═══════════════════════════════════════════════════════════════════════════
def draw_attic_diaphragm():
"""
Superior view of the epitympanum showing the diaphragm:
tensor fold + interosseous fold + medial incudal fold
with the two isthmus openings.
"""
DW, DH = 420, 280
d = Drawing(DW, DH)
# Background
d.add(Rect(0, 0, DW, DH, fillColor=BONE, strokeColor=BONE_DARK, strokeWidth=1.5, rx=6, ry=6))
# Attic floor/diaphragm overall outline
d.add(Ellipse(210, 140, 175, 110,
fillColor=colors.HexColor("#E8F5E9"),
strokeColor=GREEN, strokeWidth=2))
# Malleus head
d.add(Ellipse(175, 155, 35, 28,
fillColor=BONE_DARK, strokeColor=DARK, strokeWidth=1.5))
d.add(String(156, 150, "Malleus Head", fontName="Helvetica-Bold", fontSize=8, fillColor=DARK))
# Incus body
d.add(Ellipse(240, 150, 32, 25,
fillColor=BONE_DARK, strokeColor=DARK, strokeWidth=1.5))
d.add(String(226, 144, "Incus Body", fontName="Helvetica-Bold", fontSize=8, fillColor=DARK))
# Tensor fold (anterior - from lateral wall to malleus neck, runs anteriorly)
d.add(Line(70, 155, 140, 155, strokeColor=MUCOSA, strokeWidth=4))
d.add(String(72, 162, "Tensor Fold (Anterior Malleolar Fold)", fontName="Helvetica-Bold",
fontSize=7.5, fillColor=MUCOSA))
# Interosseous fold (between malleus and incus)
d.add(Line(208, 150, 210, 150, strokeColor=PURPLE, strokeWidth=4)) # tiny gap visualised
d.add(String(190, 130, "Interosseous fold", fontName="Helvetica", fontSize=7.5, fillColor=PURPLE))
# Medial incudal fold (incus to medial wall)
d.add(Line(272, 152, 340, 152, strokeColor=MUCOSA, strokeWidth=4))
d.add(String(278, 162, "Medial Incudal Fold", fontName="Helvetica-Bold",
fontSize=7.5, fillColor=MUCOSA))
# Isthmus anticus (gap - anterior)
d.add(Rect(140, 143, 33, 22,
fillColor=AMBER_LIGHT, strokeColor=AMBER, strokeWidth=1.5))
d.add(String(142, 155, "Isthmus", fontName="Helvetica-Bold", fontSize=7, fillColor=AMBER))
d.add(String(142, 146, "Anticus", fontName="Helvetica-Bold", fontSize=7, fillColor=AMBER))
# Isthmus posticus (gap - posterior)
d.add(Rect(275, 143, 33, 22,
fillColor=AMBER_LIGHT, strokeColor=AMBER, strokeWidth=1.5))
d.add(String(277, 155, "Isthmus", fontName="Helvetica-Bold", fontSize=7, fillColor=AMBER))
d.add(String(277, 146, "Posticus", fontName="Helvetica-Bold", fontSize=7, fillColor=AMBER))
# Arrows through isthmi (ventilation from below = upward in coronal; here = inward)
d.add(Line(157, 130, 157, 143, strokeColor=AMBER, strokeWidth=2))
d.add(Polygon([157, 143, 153, 137, 161, 137], fillColor=AMBER, strokeColor=AMBER))
d.add(Line(291, 130, 291, 143, strokeColor=AMBER, strokeWidth=2))
d.add(Polygon([291, 143, 287, 137, 295, 137], fillColor=AMBER, strokeColor=AMBER))
d.add(String(125, 118, "Air enters via isthmi", fontName="Helvetica-Oblique",
fontSize=8, fillColor=AMBER))
# Superior malleolar fold (lateral to malleus head)
d.add(Line(148, 183, 170, 183, strokeColor=AMBER, strokeWidth=2.5))
d.add(String(95, 188, "Sup. Malleolar Fold", fontName="Helvetica", fontSize=7.5, fillColor=AMBER))
# Posterior incudal ligament
d.add(Line(270, 155, 320, 175, strokeColor=PURPLE, strokeWidth=2,
strokeDashArray=[4,2]))
d.add(String(295, 180, "Post. Incudal Lig.", fontName="Helvetica", fontSize=7, fillColor=PURPLE))
# Anterior (ant.epitympanum) label
d.add(String(88, 230, "ANTERIOR", fontName="Helvetica-Bold", fontSize=9, fillColor=DARK))
d.add(Line(135, 233, 180, 233, strokeColor=DARK, strokeWidth=0.8))
# Posterior label
d.add(String(285, 230, "POSTERIOR", fontName="Helvetica-Bold", fontSize=9, fillColor=DARK))
d.add(Line(265, 233, 285, 233, strokeColor=DARK, strokeWidth=0.8))
# Lateral label
d.add(String(60, 55, "LATERAL", fontName="Helvetica-Bold", fontSize=9, fillColor=DARK))
# Medial label
d.add(String(335, 55, "MEDIAL", fontName="Helvetica-Bold", fontSize=9, fillColor=DARK))
# COG bony septum
d.add(Line(175, 185, 175, 220, strokeColor=DARK, strokeWidth=2))
d.add(String(150, 222, "Cog (bony septum)", fontName="Helvetica-Oblique",
fontSize=7, fillColor=DARK))
# Title
d.add(String(95, 260, "Superior View: Attic Diaphragm and Isthmus Tympani",
fontName="Helvetica-Bold", fontSize=10, fillColor=DARK))
d.add(Rect(1, 1, DW-2, DH-2,
fillColor=None, strokeColor=TEAL_MID, strokeWidth=1.5, rx=6, ry=6))
return d
# ═══════════════════════════════════════════════════════════════════════════
# PAGE 3 DIAGRAM – Cholesteatoma spread via folds
# ═══════════════════════════════════════════════════════════════════════════
def draw_cholesteatoma_spread():
DW, DH = 450, 300
d = Drawing(DW, DH)
d.add(Rect(0, 0, DW, DH, fillColor=BONE, strokeColor=BONE_DARK, strokeWidth=1.5, rx=6, ry=6))
# Cavities
# Mesotympanum
d.add(Rect(60, 30, 330, 100, fillColor=colors.HexColor("#D6EEF7"),
strokeColor=TEAL_MID, strokeWidth=1.2))
d.add(String(68, 50, "MESOTYMPANUM", fontName="Helvetica-Bold", fontSize=8, fillColor=TEAL))
# Epitympanum
d.add(Rect(60, 130, 330, 80, fillColor=colors.HexColor("#C8E6C9"),
strokeColor=GREEN, strokeWidth=1.2))
d.add(String(68, 195, "EPITYMPANUM", fontName="Helvetica-Bold", fontSize=8, fillColor=GREEN))
# Prussak's space (left side)
d.add(Rect(60, 130, 70, 80, fillColor=RED_LIGHT, strokeColor=RED, strokeWidth=1.5,
strokeDashArray=[4,2]))
d.add(String(63, 185, "Prussak's", fontName="Helvetica-Bold", fontSize=7.5, fillColor=RED))
d.add(String(66, 175, "Space", fontName="Helvetica-Bold", fontSize=7.5, fillColor=RED))
# Mastoid antrum
d.add(Rect(390, 100, 55, 130, fillColor=colors.HexColor("#FFF9C4"),
strokeColor=colors.HexColor("#F9A825"), strokeWidth=1.2))
d.add(String(393, 165, "Mastoid", fontName="Helvetica-Bold", fontSize=7.5,
fillColor=colors.HexColor("#F57F17")))
d.add(String(393, 155, "Antrum", fontName="Helvetica-Bold", fontSize=7.5,
fillColor=colors.HexColor("#F57F17")))
# Aditus ad antrum
d.add(Rect(390, 130, 10, 50, fillColor=colors.HexColor("#FFF9C4"),
strokeColor=colors.HexColor("#F9A825"), strokeWidth=1))
d.add(String(355, 157, "Aditus", fontName="Helvetica", fontSize=7,
fillColor=colors.HexColor("#F57F17")))
# Cholesteatoma spread arrows (numbered steps)
arrow_col = colors.HexColor("#C62828")
# 1: Pars flaccida retraction → Prussak's
d.add(String(18, 170, "①", fontName="Helvetica-Bold", fontSize=11, fillColor=arrow_col))
d.add(Line(48, 170, 60, 170, strokeColor=arrow_col, strokeWidth=2))
d.add(Polygon([60, 170, 54, 166, 54, 174], fillColor=arrow_col, strokeColor=arrow_col))
# 2: Prussak's → posterior epitympanum
d.add(String(138, 185, "②", fontName="Helvetica-Bold", fontSize=11, fillColor=arrow_col))
d.add(Line(148, 170, 200, 170, strokeColor=arrow_col, strokeWidth=2))
d.add(Polygon([200, 170, 194, 166, 194, 174], fillColor=arrow_col, strokeColor=arrow_col))
# 3: Post epitympanum → antrum via aditus
d.add(String(330, 185, "③", fontName="Helvetica-Bold", fontSize=11, fillColor=arrow_col))
d.add(Line(370, 170, 390, 170, strokeColor=arrow_col, strokeWidth=2))
d.add(Polygon([390, 170, 384, 166, 384, 174], fillColor=arrow_col, strokeColor=arrow_col))
# 4: Down to posterior mesotympanum
d.add(String(310, 155, "④", fontName="Helvetica-Bold", fontSize=11, fillColor=arrow_col))
d.add(Line(330, 145, 330, 130, strokeColor=arrow_col, strokeWidth=2))
d.add(Polygon([330, 130, 326, 137, 334, 137], fillColor=arrow_col, strokeColor=arrow_col))
# Ossicles (schematic)
# Malleus
d.add(Ellipse(195, 175, 20, 16, fillColor=BONE_DARK, strokeColor=DARK, strokeWidth=1.2))
d.add(String(183, 169, "Malleus", fontName="Helvetica", fontSize=6.5, fillColor=DARK))
# Incus
d.add(Ellipse(245, 172, 18, 14, fillColor=BONE_DARK, strokeColor=DARK, strokeWidth=1.2))
d.add(String(237, 166, "Incus", fontName="Helvetica", fontSize=6.5, fillColor=DARK))
# Fold diaphragm line
d.add(Line(130, 130, 390, 130, strokeColor=MUCOSA, strokeWidth=3))
d.add(String(155, 118, "Mucosal fold diaphragm (tensor + interosseous + medial incudal)",
fontName="Helvetica-Oblique", fontSize=7, fillColor=MUCOSA))
# Blocked isthmus marker
d.add(Rect(150, 124, 22, 12, fillColor=colors.HexColor("#FF8F00"),
strokeColor=AMBER, strokeWidth=1))
d.add(String(153, 127, "Isthmus", fontName="Helvetica-Bold", fontSize=6, fillColor=DARK))
d.add(String(156, 120, "⚠ Risk", fontName="Helvetica-Bold", fontSize=6, fillColor=DARK))
# Pars flaccida
d.add(Rect(40, 130, 18, 80, fillColor=colors.HexColor("#FFCCBC"),
strokeColor=colors.HexColor("#BF360C"), strokeWidth=1.5))
d.add(String(8, 168, "Pars", fontName="Helvetica-Bold", fontSize=7,
fillColor=colors.HexColor("#BF360C")))
d.add(String(5, 159, "Flaccida", fontName="Helvetica-Bold", fontSize=7,
fillColor=colors.HexColor("#BF360C")))
# ET opening bottom left
d.add(Rect(60, 12, 50, 18, fillColor=colors.HexColor("#BFD8F0"),
strokeColor=TEAL, strokeWidth=1))
d.add(String(62, 18, "Eustachian Tube", fontName="Helvetica", fontSize=7, fillColor=TEAL))
d.add(String(80, 276, "Cholesteatoma Spread Pathways via Mucosal Fold Compartments",
fontName="Helvetica-Bold", fontSize=10, fillColor=DARK))
d.add(Rect(1, 1, DW-2, DH-2,
fillColor=None, strokeColor=RED, strokeWidth=1.5, rx=6, ry=6))
return d
# ═══════════════════════════════════════════════════════════════════════════
# MUCOCILIARY PATHWAYS DIAGRAM
# ═══════════════════════════════════════════════════════════════════════════
def draw_mucociliary():
DW, DH = 420, 220
d = Drawing(DW, DH)
d.add(Rect(0, 0, DW, DH, fillColor=GRAY_LIGHT, strokeColor=GRAY_MID, strokeWidth=1.5, rx=6, ry=6))
# Middle ear outline
d.add(Rect(40, 30, 340, 155, fillColor=colors.HexColor("#E8F4F8"),
strokeColor=TEAL, strokeWidth=1.5, rx=4, ry=4))
# Eustachian tube (right)
d.add(Rect(380, 65, 38, 30, fillColor=colors.HexColor("#BFD8F0"),
strokeColor=TEAL, strokeWidth=1.2))
d.add(String(382, 78, "Eust.", fontName="Helvetica-Bold", fontSize=7.5, fillColor=TEAL))
d.add(String(382, 69, "Tube", fontName="Helvetica-Bold", fontSize=7.5, fillColor=TEAL))
# Promontory (medial wall - left)
d.add(Rect(25, 60, 15, 80, fillColor=BONE_DARK, strokeColor=DARK, strokeWidth=1))
d.add(String(10, 97, "Prom.", fontName="Helvetica", fontSize=7, fillColor=DARK))
# ── Three mucociliary pathways ───────────────────────────────────────────
# 1. Epitympanic (top, green)
epi_y = 165
for x in range(60, 370, 18):
d.add(Line(x, epi_y, x+12, epi_y, strokeColor=GREEN, strokeWidth=2))
d.add(Polygon([x+12, epi_y, x+7, epi_y+3, x+7, epi_y-3],
fillColor=GREEN, strokeColor=GREEN))
d.add(String(50, 175, "Epitympanic pathway", fontName="Helvetica-Bold",
fontSize=8, fillColor=GREEN))
# 2. Promontorial (middle, blue)
prom_y = 110
for x in range(60, 370, 18):
d.add(Line(x, prom_y, x+12, prom_y, strokeColor=TEAL, strokeWidth=2))
d.add(Polygon([x+12, prom_y, x+7, prom_y+3, x+7, prom_y-3],
fillColor=TEAL, strokeColor=TEAL))
d.add(String(50, 120, "Promontorial pathway", fontName="Helvetica-Bold",
fontSize=8, fillColor=TEAL))
# 3. Hypotympanic (bottom, amber - LARGEST)
hypo_y = 50
for x in range(60, 370, 18):
d.add(Line(x, hypo_y, x+12, hypo_y, strokeColor=AMBER, strokeWidth=2.5))
d.add(Polygon([x+12, hypo_y, x+7, hypo_y+3, x+7, hypo_y-3],
fillColor=AMBER, strokeColor=AMBER))
d.add(String(50, 40, "Hypotympanic pathway (largest)", fontName="Helvetica-Bold",
fontSize=8, fillColor=AMBER))
# All converge at ET
d.add(Line(370, 165, 380, 80, strokeColor=GREEN, strokeWidth=1.5))
d.add(Line(370, 110, 380, 80, strokeColor=TEAL, strokeWidth=1.5))
d.add(Line(370, 50, 380, 80, strokeColor=AMBER, strokeWidth=2))
d.add(String(70, 200, "Three Mucociliary Pathways - All converge at the ET orifice",
fontName="Helvetica-Bold", fontSize=9.5, fillColor=DARK))
d.add(Rect(1, 1, DW-2, DH-2, fillColor=None, strokeColor=TEAL_MID,
strokeWidth=1.2, rx=6, ry=6))
return d
# ═══════════════════════════════════════════════════════════════════════════
# BUILD THE PDF
# ═══════════════════════════════════════════════════════════════════════════
def build_pdf(out_path):
doc = SimpleDocTemplate(
out_path,
pagesize=A4,
leftMargin=18*mm, rightMargin=18*mm,
topMargin=16*mm, bottomMargin=16*mm,
title="Middle Ear Mucosal Fold Anatomy & Ventilation Pathways",
author="Orris Medical Reference"
)
story = []
# ── COVER BANNER ─────────────────────────────────────────────────────────
cover = Drawing(W - 36*mm, 60)
cover.add(Rect(0, 0, W - 36*mm, 60, fillColor=TEAL, strokeColor=None, rx=6, ry=6))
cover.add(String(18, 32, "MIDDLE EAR MUCOSAL FOLD ANATOMY",
fontName="Helvetica-Bold", fontSize=19, fillColor=colors.white))
cover.add(String(18, 14, "AND VENTILATION PATHWAYS | Visual Reference Guide",
fontName="Helvetica", fontSize=12, fillColor=colors.HexColor("#B2EBF2")))
story.append(cover)
story.append(Spacer(1, 6*mm))
# ── Introductory text ────────────────────────────────────────────────────
story.append(Paragraph(
"The middle ear mucosa behaves like peritoneum - it drapes over ossicles, ligaments, tendons and bony walls "
"to form a series of folds and recesses. These folds compartmentalise the tympanic cavity and directly govern "
"airflow between the mesotympanum, epitympanum and mastoid system. "
"Understanding their anatomy is essential for explaining otitis media, cholesteatoma formation and surgical planning.",
BODY_STYLE))
story.append(Spacer(1, 3*mm))
# ══════════════════════════════════════════════════════════════════════════
# SECTION 1 - Coronal Cross-section
# ══════════════════════════════════════════════════════════════════════════
story.append(section_banner("SECTION 1 — Coronal Cross-Section: Key Folds and Spaces"))
story.append(Spacer(1, 3*mm))
story.append(draw_coronal_section())
story.append(Paragraph(
"Fig 1. Schematic coronal cross-section of the right middle ear. "
"The mucosal fold diaphragm (tensor fold + medial incudal fold) separates the epitympanum (green) from "
"the mesotympanum (blue). The isthmus tympani anticus and posticus are the sole air conduits into the attic. "
"Prussak's space (pink) lies lateral to the malleus neck and medial to the pars flaccida.",
CAPTION_STYLE))
story.append(Spacer(1, 4*mm))
# ── Legend ──────────────────────────────────────────────────────────────
story.append(section_banner("COLOUR KEY", color=colors.HexColor("#455A64"), w=W - 36*mm))
story.append(Spacer(1, 2*mm))
key_data = [
[colors.HexColor("#D6EEF7"), "Mesotympanum (freely ventilated)"],
[colors.HexColor("#C8E6C9"), "Epitympanum / Attic (isthmus-dependent)"],
[RED_LIGHT, "Prussak's space"],
[AMBER_LIGHT, "Isthmus tympani anticus / posticus"],
[MUCOSA, "Mucosal folds (tensor, interosseous, medial incudal)"],
[AMBER, "Plica stapedis / superior malleolar fold"],
[PURPLE, "Incudal folds / posterior incudal ligament"],
[BONE_DARK, "Ossicles (malleus, incus, stapes)"],
[TEAL, "Airflow / ventilation direction"],
]
for col, label in key_data:
kd = Drawing(12, 10)
kd.add(Rect(0, 1, 11, 9, fillColor=col,
strokeColor=colors.HexColor("#777777"), strokeWidth=0.5))
row_tbl = Table([[kd, Paragraph(label, KEY_STYLE)]], colWidths=[16, 130*mm])
row_tbl.setStyle(TableStyle([("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("LEFTPADDING",(0,0),(-1,-1),2),
("TOPPADDING",(0,0),(-1,-1),1),
("BOTTOMPADDING",(0,0),(-1,-1),1)]))
story.append(row_tbl)
story.append(PageBreak())
# ══════════════════════════════════════════════════════════════════════════
# SECTION 2 – Named Folds Reference Table
# ══════════════════════════════════════════════════════════════════════════
story.append(section_banner("SECTION 2 — Named Mucosal Folds: Anatomy and Function"))
story.append(Spacer(1, 4*mm))
header_style = ParagraphStyle("TH", parent=base["Normal"],
fontSize=9, fontName="Helvetica-Bold", textColor=colors.white, alignment=TA_CENTER)
cell_style = ParagraphStyle("TC", parent=base["Normal"],
fontSize=8.5, fontName="Helvetica", textColor=DARK, alignment=TA_LEFT, leading=13)
def th(txt): return Paragraph(txt, header_style)
def td(txt): return Paragraph(txt, cell_style)
table_data = [
[th("Fold / Structure"), th("Location"), th("Boundaries / Attachments"), th("Ventilation Role")],
[td("<b>Tensor fold</b>\n(Ant. malleolar fold)"),
td("Anterior attic diaphragm"),
td("Tensor tympani tendon → lateral malleolar fold"),
td("Forms the anterior limb of the epitympanum diaphragm; closes off attic anteriorly")],
[td("<b>Lateral malleolar fold</b>"),
td("Lateral wall of Prussak's space"),
td("Runs from notch of Rivinus to malleus neck"),
td("Defines Prussak's space; restricts lateral attic ventilation")],
[td("<b>Superior malleolar fold</b>"),
td("Superior, over malleus head"),
td("Tegmen → malleus head / neck"),
td("Suspends malleus; part of attic roof lining")],
[td("<b>Interosseous fold</b>"),
td("Between malleus head and incus body"),
td("Malleus head → incus body"),
td("Central component of the diaphragm; partially closes the attic floor")],
[td("<b>Medial incudal fold</b>"),
td("Medial to incus body"),
td("Incus body → medial attic wall"),
td("Closes off medial attic; with tensor and interosseous folds creates complete diaphragm")],
[td("<b>Superior incudal fold</b>"),
td("Superior to incus body"),
td("Incus → tegmen tympani"),
td("Suspends incus; carries blood supply")],
[td("<b>Posterior incudal ligament</b>"),
td("Posterior to short process of incus"),
td("Short process of incus → fossa incudis"),
td("Tethers incus; not a major airflow determinant")],
[td("<b>Plica stapedis</b>\n(Obturatoria stapedi)"),
td("Around stapes"),
td("Envelops stapes superstructure and stapedius tendon"),
td("Wraps stapes; lies between the two isthmi")],
[td("<b>Anterior fold of von Tröltsch</b>"),
td("Anterior to malleus handle"),
td("Between anterior canal wall and malleus handle"),
td("Bounds the anterior recess (pouch); may retain squamous debris")],
[td("<b>Posterior fold of von Tröltsch</b>"),
td("Posterior to malleus handle"),
td("Posterior canal wall → malleus handle"),
td("Bounds the posterior recess; cholesteatoma can originate here")],
]
col_ws = [42*mm, 30*mm, 62*mm, 64*mm]
tbl = Table(table_data, colWidths=col_ws, repeatRows=1)
tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), TEAL),
("ROWBACKGROUNDS",(0,1), (-1,-1), [colors.white, TEAL_LIGHT]),
("GRID", (0,0), (-1,-1), 0.5, GRAY_MID),
("VALIGN", (0,0), (-1,-1), "TOP"),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("LINEBELOW", (0,0), (-1,0), 1.5, TEAL),
]))
story.append(tbl)
story.append(Spacer(1, 6*mm))
# ── Attic diaphragm diagram ──────────────────────────────────────────────
story.append(section_banner("Attic Diaphragm — Superior View", color=GREEN))
story.append(Spacer(1, 3*mm))
story.append(draw_attic_diaphragm())
story.append(Paragraph(
"Fig 2. Superior (axial) view of the epitympanum. The tensor fold (anteriorly), interosseous fold "
"(centrally) and medial incudal fold (posteriorly) together form the attic diaphragm. "
"The only openings in this diaphragm are the isthmus tympani anticus (anterior to stapes) "
"and the isthmus tympani posticus (posterior to stapes).",
CAPTION_STYLE))
story.append(PageBreak())
# ══════════════════════════════════════════════════════════════════════════
# SECTION 3 – Ventilation Pathways
# ══════════════════════════════════════════════════════════════════════════
story.append(section_banner("SECTION 3 — Ventilation Pathways: Mesotympanum vs. Epitympanum"))
story.append(Spacer(1, 4*mm))
# Two-column layout: mesotympanum vs. epitympanum
meso_text = [
Paragraph("<b>Mesotympanum</b>", H2_STYLE),
Paragraph(
"Directly receives air from the Eustachian tube opening. "
"Air floods the mesotympanum freely during each swallowing act that opens the ET. "
"This space is therefore well-ventilated under normal conditions.",
BODY_STYLE),
Paragraph("<b>Key features:</b>", BOLD_STYLE),
Paragraph("- Bounded by TM laterally, promontory medially", BODY_STYLE),
Paragraph("- Contains oval window, round window, stapes footplate", BODY_STYLE),
Paragraph("- Sinus tympani: posterior recess that may harbor occult cholesteatoma", BODY_STYLE),
Paragraph("- ET orifice is the direct air source", BODY_STYLE),
]
epi_text = [
Paragraph("<b>Epitympanum (Attic)</b>", H2_STYLE),
Paragraph(
"Does <b>not</b> receive air directly from the ET. "
"Its only air supply is through the two tympanic isthmi - narrow openings in the mucosal fold diaphragm. "
"Any process blocking the isthmus cuts off epitympanic ventilation.",
BODY_STYLE),
Paragraph("<b>Subdivisions:</b>", BOLD_STYLE),
Paragraph("- Prussak's space (lateral, between pars flaccida and malleus neck)", BODY_STYLE),
Paragraph("- Anterior epitympanum (supratubal recess, anterior to cog)", BODY_STYLE),
Paragraph("- Posterior epitympanum (communicates with aditus ad antrum)", BODY_STYLE),
Paragraph("- Isthmus anticus: anterior to stapes superstructure", BODY_STYLE),
Paragraph("- Isthmus posticus: posterior to stapes superstructure", BODY_STYLE),
]
meso_inner = [[p] for p in meso_text]
epi_inner = [[p] for p in epi_text]
from reportlab.platypus import FrameBreak
col_w = (W - 36*mm - 8*mm) / 2
meso_tbl = Table([[p] for p in meso_text], colWidths=[col_w])
meso_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), colors.HexColor("#E8F4FB")),
("BOX", (0,0), (-1,-1), 1, TEAL),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 6),
]))
epi_tbl = Table([[p] for p in epi_text], colWidths=[col_w])
epi_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), colors.HexColor("#E8F5E9")),
("BOX", (0,0), (-1,-1), 1, GREEN),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 6),
]))
side_by_side = Table([[meso_tbl, epi_tbl]], colWidths=[col_w + 4*mm, col_w + 4*mm])
side_by_side.setStyle(TableStyle([
("VALIGN", (0,0), (-1,-1), "TOP"),
("LEFTPADDING", (0,0), (-1,-1), 0),
("RIGHTPADDING", (0,0), (-1,-1), 4),
]))
story.append(side_by_side)
story.append(Spacer(1, 5*mm))
# Mucociliary diagram
story.append(section_banner("Mucociliary Clearance Pathways", color=GREEN))
story.append(Spacer(1, 3*mm))
story.append(draw_mucociliary())
story.append(Paragraph(
"Fig 3. Three mucociliary clearance pathways within the tympanic cavity. "
"All three converge at the Eustachian tube tympanic orifice. "
"The hypotympanic pathway is the largest and most important. "
"Ciliated epithelium is more widespread in younger individuals.",
CAPTION_STYLE))
story.append(Spacer(1, 4*mm))
# ── Isthmus box ──────────────────────────────────────────────────────────
isthmus_data = [
[Paragraph("<b>THE TYMPANIC ISTHMUS — Summary</b>", ParagraphStyle("IH",parent=base["Normal"],
fontSize=10, fontName="Helvetica-Bold", textColor=colors.white))],
[Paragraph(
"<b>Isthmus tympani anticus:</b> Located anterior to the stapes superstructure, "
"between the end of the tensor fold and the plica stapedis.<br/><br/>"
"<b>Isthmus tympani posticus:</b> Located posterior to the stapes superstructure, "
"between the medial incudal fold and the posterior mesotympanic wall.<br/><br/>"
"Together these two openings are the <b>sole route</b> for air to enter the epitympanum "
"from the Eustachian tube. The mesotympanum acts as an antechamber; air rises "
"through the isthmi into the attic by the pressure gradient created with each ET opening.<br/><br/>"
"<b>Normal dimensions:</b> Both isthmi are narrow (approximately 1–2 mm). "
"Even small amounts of mucosal oedema or granulation tissue can block them entirely.",
BOX_STYLE)],
]
isthmus_tbl = Table(isthmus_data, colWidths=[W - 36*mm])
isthmus_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (0,0), AMBER),
("BACKGROUND", (0,1), (0,1), AMBER_LIGHT),
("BOX", (0,0), (-1,-1), 1.5, AMBER),
("INNERGRID", (0,0), (-1,-1), 0.5, AMBER),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 8),
]))
story.append(isthmus_tbl)
story.append(PageBreak())
# ══════════════════════════════════════════════════════════════════════════
# SECTION 4 – Cholesteatoma and Pathology
# ══════════════════════════════════════════════════════════════════════════
story.append(section_banner("SECTION 4 — Pathology: Isthmus Obstruction and Cholesteatoma",
color=RED))
story.append(Spacer(1, 4*mm))
story.append(draw_cholesteatoma_spread())
story.append(Paragraph(
"Fig 4. Cholesteatoma spread pathways defined by mucosal fold compartments. "
"① Pars flaccida retraction enters Prussak's space. "
"② Keratin migrates posteriorly through the posterior epitympanum. "
"③ Extension into the mastoid antrum via the aditus ad antrum. "
"④ Inferior spread to the posterior mesotympanum. "
"Obstruction of the isthmus tympani (⚠) perpetuates negative pressure and drives retraction.",
CAPTION_STYLE))
story.append(Spacer(1, 4*mm))
# Pathology table
story.append(section_banner("Clinical Consequences of Fold/Isthmus Pathology", color=RED))
story.append(Spacer(1, 3*mm))
path_data = [
[th("Pathology"), th("Mechanism"), th("Result"), th("Clinical Correlate")],
[td("<b>Isthmus obstruction</b>"),
td("Mucosal oedema, granulation, fold fibrosis, or cholesteatoma mass blocks one or both isthmi"),
td("Loss of epitympanic air; negative pressure builds in attic"),
td("Attic retraction pocket, pars flaccida cholesteatoma")],
[td("<b>Congenital fold defects</b>"),
td("Incomplete development of tensor or interosseous fold during embryogenesis"),
td("Abnormal airflow compartmentalisation from birth"),
td("Recurrent otitis media, persistent effusion in attic")],
[td("<b>Mucosal inflammation\n(OME/CSOM)</b>"),
td("Subepithelial oedema and fibrous thickening of folds reduces isthmus cross-section"),
td("Progressive epitympanic under-ventilation"),
td("Chronic glue ear; bimodal effusion in meso- and epitympanum")],
[td("<b>Prussak's space\nretraction</b>"),
td("Lateral malleolar fold weakness + chronic negative pressure allows pars flaccida to retract medially"),
td("Keratin accumulates, expands via fold-defined pathways"),
td("Attic cholesteatoma; scutum erosion on CT; ossicular destruction")],
[td("<b>Cholesteatoma\nblocking aditus</b>"),
td("Expanding matrix in posterior epitympanum obstructs aditus ad antrum"),
td("Mastoid ventilation fails; closed mastoid air cell system"),
td("Coalescent mastoid disease; epidural/lateral sinus involvement")],
]
path_tbl = Table(path_data, colWidths=[36*mm, 50*mm, 46*mm, 66*mm], repeatRows=1)
path_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), RED),
("ROWBACKGROUNDS",(0,1), (-1,-1), [colors.white, RED_LIGHT]),
("GRID", (0,0), (-1,-1), 0.5, GRAY_MID),
("VALIGN", (0,0), (-1,-1), "TOP"),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("LINEBELOW", (0,0), (-1,0), 1.5, RED),
]))
story.append(path_tbl)
story.append(PageBreak())
# ══════════════════════════════════════════════════════════════════════════
# SECTION 5 – Quick-reference summary card
# ══════════════════════════════════════════════════════════════════════════
story.append(section_banner("SECTION 5 — Surgical Anatomy Quick-Reference", color=PURPLE))
story.append(Spacer(1, 4*mm))
# Surgical pearls
pearls = [
("<b>The isthmus anticus</b> lies anterior to the stapes superstructure, "
"between the tensor fold edge and the plica stapedis."),
("<b>The isthmus posticus</b> lies posterior to the stapes, between "
"the medial incudal fold and the posterior tympanic wall."),
("<b>The cog</b> is a bony spur from the tegmen in the coronal plane; "
"it divides the anterior from posterior epitympanum and must be removed "
"to fully visualise the supratubal recess."),
("<b>Prussak's space</b>: medial to pars flaccida, lateral to malleus neck, "
"inferior to the lateral malleolar fold. The origin of most attic cholesteatomas."),
("<b>Removal of incus + malleus head</b> is required in attic cholesteatoma surgery "
"to access and clear disease in the anterior epitympanum."),
("<b>The posterior pouch of von Tröltsch</b> (posterior to malleus handle, "
"anterior to chorda tympani) is a starting point for pars tensa retractions."),
("<b>Mucociliary flow</b> runs in three streams (epitympanic, promontorial, hypotympanic), "
"all converging at the ET tympanic orifice. The hypotympanic stream is the largest."),
("<b>Bony dehiscence</b> of the facial nerve is most common above the oval window "
"(tympanic segment, 55%) — the nerve runs just superior to the cochleariform process "
"as a reliable landmark for its mid-portion."),
]
for i, pearl in enumerate(pearls, 1):
p_tbl = Table([[
Paragraph(f"{i}", ParagraphStyle("Num", parent=base["Normal"],
fontSize=14, fontName="Helvetica-Bold",
textColor=colors.white, alignment=TA_CENTER)),
Paragraph(pearl, BOX_STYLE)
]], colWidths=[12*mm, W - 36*mm - 16*mm])
p_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (0,0), PURPLE),
("BACKGROUND", (1,0), (1,0), PURPLE_LIGHT),
("BOX", (0,0), (-1,-1), 0.8, PURPLE),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 6),
("RIGHTPADDING", (0,0), (-1,-1), 6),
]))
story.append(p_tbl)
story.append(Spacer(1, 2*mm))
story.append(Spacer(1, 4*mm))
# Embryology note
story.append(section_banner("Embryological Note", color=colors.HexColor("#455A64")))
story.append(Spacer(1, 3*mm))
story.append(Paragraph(
"The future Eustachian tube lumen and middle ear spaces are formed by 8 months' gestation. "
"The ossicles and their associated muscles, tendons and ligaments form from the first and second "
"branchial arch mesenchyme, generating the complex series of mucosal folds as the endodermal "
"epithelium of the tubotympanic recess envelopes each structure in turn. "
"The epitympanum and mastoid antrum are the last spaces to pneumatise. "
"Congenital anomalies of the folds - particularly incomplete formation of the tensor fold - "
"can predispose to inadequate epitympanic ventilation from birth.",
BODY_STYLE))
story.append(Spacer(1, 5*mm))
# Source attribution
story.append(HRFlowable(width="100%", thickness=0.5, color=GRAY_MID))
story.append(Spacer(1, 2*mm))
story.append(Paragraph(
"<b>Sources:</b> Scott-Brown's Otorhinolaryngology Head & Neck Surgery, 8th ed. (Vol 2) — "
"Sections: 'The middle ear mucosal folds', 'The mucosa of the tympanic cavity', "
"'Derivatives of the first and second branchial arches'. | "
"Cummings Otolaryngology Head and Neck Surgery — Chapters 126 (Endoscopic Ear Anatomy, "
"Middle Ear Spaces, Fig. 126.10) and 128. | "
"Proctor B: The development of the middle ear spaces and their surgical significance (1964).",
CREDIT_STYLE))
doc.build(story)
print(f"PDF saved to: {out_path}")
if __name__ == "__main__":
out = "/home/daytona/workspace/middle-ear-guide/Mucosal_Fold_Ventilation_Guide.pdf"
build_pdf(out)