~/oral-cavity-pptx/build_pptx.py
"""
Oral Cavity: Arterial, Venous & Lymphatic Anatomy
Presentation Builder
"""
import json, subprocess, base64
from io import BytesIO
from pptx import Presentation
from pptx.util import Inches, Pt, Emu
from pptx.dml.color import RGBColor
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
from pptx.enum.shapes import MSO_SHAPE
# ── Color Palette ──────────────────────────────────────────────────────────────
C_BG = RGBColor(0x0D, 0x1B, 0x2A) # deep navy background
C_ACCENT = RGBColor(0xC8, 0x3B, 0x3B) # arterial red
C_VENOUS = RGBColor(0x3A, 0x6B, 0xC4) # venous blue
C_LYMPH = RGBColor(0x2E, 0xA6, 0x6A) # lymph green
C_GOLD = RGBColor(0xF5, 0xC5, 0x42) # section header gold
C_WHITE = RGBColor(0xFF, 0xFF, 0xFF)
C_LIGHT = RGBColor(0xE8, 0xEE, 0xF4) # light text
C_DARK_CARD = RGBColor(0x16, 0x2A, 0x40) # card background
C_MID = RGBColor(0xA0, 0xB4, 0xCC) # muted text
C_DIVIDER = RGBColor(0x2A, 0x3F, 0x5A)
FONT_MAIN = "Calibri"
FONT_TITLE = "Calibri"
# ── Helper: slide background ───────────────────────────────────────────────────
def set_bg(slide, color=C_BG):
bg = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE,
Inches(0), Inches(0),
Inches(13.333), Inches(7.5))
bg.fill.solid()
bg.fill.fore_color.rgb = color
bg.line.fill.background()
return bg
# ── Helper: add text box ───────────────────────────────────────────────────────
def tb(slide, text, x, y, w, h,
font=FONT_MAIN, size=18, bold=False, italic=False,
color=C_WHITE, align=PP_ALIGN.LEFT, wrap=True,
valign=MSO_ANCHOR.TOP):
shape = slide.shapes.add_textbox(Inches(x), Inches(y), Inches(w), Inches(h))
tf = shape.text_frame
tf.word_wrap = wrap
tf.vertical_anchor = valign
tf.margin_left = Pt(4)
tf.margin_right = Pt(4)
tf.margin_top = Pt(2)
tf.margin_bottom= Pt(2)
p = tf.paragraphs[0]
p.alignment = align
r = p.add_run()
r.text = text
r.font.name = font
r.font.size = Pt(size)
r.font.bold = bold
r.font.italic= italic
r.font.color.rgb = color
return shape
# ── Helper: coloured pill/badge ────────────────────────────────────────────────
def pill(slide, x, y, w, h, fill_color, radius=None):
shp = slide.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE,
Inches(x), Inches(y), Inches(w), Inches(h))
shp.fill.solid()
shp.fill.fore_color.rgb = fill_color
shp.line.fill.background()
return shp
def rect(slide, x, y, w, h, fill_color, line_color=None, line_w=1):
shp = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE,
Inches(x), Inches(y), Inches(w), Inches(h))
shp.fill.solid()
shp.fill.fore_color.rgb = fill_color
if line_color:
shp.line.color.rgb = line_color
shp.line.width = Pt(line_w)
else:
shp.line.fill.background()
return shp
# ── Helper: horizontal rule ───────────────────────────────────────────────────
def hline(slide, x, y, w, color=C_DIVIDER, width=1.5):
from pptx.enum.shapes import MSO_CONNECTOR
ln = slide.shapes.add_connector(MSO_CONNECTOR.STRAIGHT,
Inches(x), Inches(y),
Inches(x+w), Inches(y))
ln.line.color.rgb = color
ln.line.width = Pt(width)
# ── Helper: multi-line text box with separate paragraphs ─────────────────────
def tb_lines(slide, lines, x, y, w, h,
font=FONT_MAIN, size=16, bold=False,
color=C_LIGHT, spacing=1.15, wrap=True,
align=PP_ALIGN.LEFT):
"""lines = list of (text, indent_level, bold_override, color_override)"""
shape = slide.shapes.add_textbox(Inches(x), Inches(y), Inches(w), Inches(h))
tf = shape.text_frame
tf.word_wrap = wrap
tf.margin_left = Pt(4)
tf.margin_right = Pt(4)
tf.margin_top = Pt(2)
tf.margin_bottom= Pt(2)
first = True
from pptx.oxml.ns import qn
from lxml import etree
for (text, indent, bold_o, color_o) in lines:
if first:
p = tf.paragraphs[0]
first = False
else:
p = tf.add_paragraph()
p.alignment = align
# space after
pPr = p._p.get_or_add_pPr()
spcAft = etree.SubElement(pPr, qn('a:spcAft'))
spcPct = etree.SubElement(spcAft, qn('a:spcPct'))
spcPct.set('val', '0')
r = p.add_run()
r.text = text
r.font.name = font
r.font.size = Pt(size)
r.font.bold = bold_o if bold_o is not None else bold
r.font.color.rgb = color_o if color_o is not None else color
return shape
# ── Helper: fetch remote image → BytesIO ─────────────────────────────────────
def fetch_image(url):
result = json.loads(subprocess.check_output(
["python", "/tmp/skills/shared/scripts/fetch_images.py", url]
))
item = result[0]
if item.get("base64"):
raw = base64.b64decode(item["base64"].split(",", 1)[1])
return BytesIO(raw)
return None
# ══════════════════════════════════════════════════════════════════════════════
# PRESENTATION
# ══════════════════════════════════════════════════════════════════════════════
prs = Presentation()
prs.slide_width = Inches(13.333)
prs.slide_height = Inches(7.5)
blank = prs.slide_layouts[6]
IMAGE_FACE_ARTERY = "https://cdn.orris.care/cdss_images/335aae4b6a630052e093bc8abd9359cb78e9be1bdfebc0bace6bccb4513fbf98.png"
IMAGE_MAX_ARTERY = "https://cdn.orris.care/cdss_images/de85d349043c8d6194fbc6aad09287fd42863c6f3192a4641e70514b3358d849.png"
IMAGE_LINGUAL_VEIN = "https://cdn.orris.care/cdss_images/4fb66bf6990b9f9f9b4c18a7171d35c5d5e934c6128ccae3c44f7cc001259d30.png"
print("Fetching images...")
img_face = fetch_image(IMAGE_FACE_ARTERY)
img_max = fetch_image(IMAGE_MAX_ARTERY)
img_lv = fetch_image(IMAGE_LINGUAL_VEIN)
print("Images fetched.")
# ─────────────────────────────────────────────────────────────────────────────
# SLIDE 1 ─ TITLE
# ─────────────────────────────────────────────────────────────────────────────
slide = prs.slides.add_slide(blank)
set_bg(slide)
# Top accent bar (red)
rect(slide, 0, 0, 13.333, 0.08, C_ACCENT)
# Bottom accent bar (green-blue gradient substitute)
rect(slide, 0, 7.42, 13.333, 0.08, C_LYMPH)
# Central title card
rect(slide, 1.2, 1.6, 10.9, 4.3, C_DARK_CARD)
# Left colour strip (3 colours = arterial / venous / lymph)
rect(slide, 1.2, 1.6, 0.22, 1.43, C_ACCENT)
rect(slide, 1.2, 3.03, 0.22, 1.43, C_VENOUS)
rect(slide, 1.2, 4.47, 0.22, 1.43, C_LYMPH)
tb(slide, "ORAL CAVITY", 2.0, 1.85, 10.0, 0.8,
font=FONT_TITLE, size=42, bold=True, color=C_WHITE, align=PP_ALIGN.LEFT)
tb(slide, "Vascular & Lymphatic Anatomy", 2.0, 2.55, 10.0, 0.7,
font=FONT_TITLE, size=28, bold=False, color=C_GOLD, align=PP_ALIGN.LEFT)
hline(slide, 2.0, 3.35, 9.1, C_DIVIDER, 1.0)
tb(slide, "Arterial Supply | Venous Drainage | Lymphatic Drainage", 2.0, 3.5, 10.0, 0.6,
font=FONT_MAIN, size=18, color=C_MID, align=PP_ALIGN.LEFT)
tb(slide, "Sources: Gray's Anatomy for Students | Scott-Brown's Otorhinolaryngology | K.J. Lee's Essential Otolaryngology",
2.0, 5.0, 9.5, 0.5, font=FONT_MAIN, size=11, color=C_MID, italic=True)
# ─────────────────────────────────────────────────────────────────────────────
# SLIDE 2 ─ OVERVIEW / OVERVIEW OF BLOOD SUPPLY
# ─────────────────────────────────────────────────────────────────────────────
slide = prs.slides.add_slide(blank)
set_bg(slide)
rect(slide, 0, 0, 13.333, 0.08, C_ACCENT)
# Slide header
rect(slide, 0, 0.08, 13.333, 0.75, C_DARK_CARD)
tb(slide, "Overview of Oral Cavity Blood Supply", 0.3, 0.1, 10.0, 0.65,
font=FONT_TITLE, size=26, bold=True, color=C_WHITE)
# Three column cards
cols = [
("ARTERIAL", C_ACCENT, 0.25,
["External Carotid Artery (ECA)", "is the dominant source",
"", "3 major branches:", " Facial Artery", " Lingual Artery", " Maxillary Artery",
"", "Minor: Internal Carotid (ophthalmic a.)"]),
("VENOUS", C_VENOUS, 4.7,
["2 main draining channels:", "", " Facial Vein", " Pterygoid Venous Plexus",
"", "Both ultimately drain to:", " Internal Jugular Vein (IJV)",
"", "Valveless connections to cavernous sinus"]),
("LYMPHATIC", C_LYMPH, 9.1,
["3 principal node groups:", "", " Submental (Level IA)", " Submandibular (Level IB)", " Jugulodigastric (Level IIA)",
"", "All drain to deep cervical chain",
"", "Midline structures drain bilaterally"]),
]
for (label, color, cx, lines) in cols:
rect(slide, cx, 1.05, 4.1, 5.9, C_DARK_CARD)
rect(slide, cx, 1.05, 4.1, 0.5, color)
tb(slide, label, cx+0.1, 1.08, 3.9, 0.42, font=FONT_TITLE,
size=18, bold=True, color=C_WHITE, align=PP_ALIGN.CENTER)
y = 1.7
for line in lines:
is_header = not line.startswith(" ") and line != "" and ":" in line
color_l = C_GOLD if is_header else (C_MID if line == "" else C_LIGHT)
bold_l = True if is_header else False
size_l = 13 if not is_header and line.startswith(" ") else 14
tb(slide, line, cx+0.15, y, 3.8, 0.36,
font=FONT_MAIN, size=size_l, bold=bold_l, color=color_l)
y += 0.33
# ─────────────────────────────────────────────────────────────────────────────
# SLIDE 3 ─ FACIAL ARTERY
# ─────────────────────────────────────────────────────────────────────────────
slide = prs.slides.add_slide(blank)
set_bg(slide)
rect(slide, 0, 0, 13.333, 0.08, C_ACCENT)
rect(slide, 0, 0.08, 13.333, 0.75, C_DARK_CARD)
tb(slide, "Facial Artery", 0.3, 0.1, 6.0, 0.65,
font=FONT_TITLE, size=26, bold=True, color=C_ACCENT)
tb(slide, "ARTERIAL SUPPLY", 8.0, 0.1, 5.0, 0.65,
font=FONT_TITLE, size=13, bold=True, color=C_MID, align=PP_ALIGN.RIGHT)
# Left panel - text
rect(slide, 0.2, 1.0, 6.0, 6.3, C_DARK_CARD)
pill(slide, 0.3, 1.1, 2.0, 0.38, C_ACCENT)
tb(slide, "ORIGIN & COURSE", 0.3, 1.13, 2.0, 0.32,
font=FONT_MAIN, size=12, bold=True, color=C_WHITE, align=PP_ALIGN.CENTER)
lines_fa_course = [
("Branch of External Carotid Artery (ECA)", 0, True, C_LIGHT),
("Passes deep through neck structures", 0, False, C_LIGHT),
("Appears at lower border of mandible", 0, False, C_LIGHT),
("(posterior to submandibular gland)", 0, False, C_MID),
("Pulse felt at inferior border of mandible", 0, False, C_LIGHT),
("(anterior to masseter)", 0, False, C_MID),
("Runs tortuous course upward + medially", 0, False, C_LIGHT),
("Terminates as angular artery at medial orbit", 0, False, C_LIGHT),
]
tb_lines(slide, lines_fa_course, 0.35, 1.55, 5.8, 2.5,
font=FONT_MAIN, size=13, color=C_LIGHT)
pill(slide, 0.3, 3.85, 2.5, 0.38, C_ACCENT)
tb(slide, "BRANCHES TO ORAL CAVITY", 0.3, 3.88, 2.5, 0.32,
font=FONT_MAIN, size=12, bold=True, color=C_WHITE, align=PP_ALIGN.CENTER)
branches_fa = [
("Inferior Labial Artery -> Lower lip", 0, True, C_GOLD),
("Superior Labial Artery -> Upper lip + nasal septum branch", 0, True, C_GOLD),
("Submental Artery -> Floor of mouth, chin, submandibular region", 0, False, C_LIGHT),
("Ascending Palatine Artery -> Soft palate, tonsil", 0, False, C_LIGHT),
("Tonsillar Branch -> Tonsil, root of tongue", 0, False, C_LIGHT),
("Labial branches anastomose across midline", 0, False, C_MID),
("(connects both ECA circulations)", 0, False, C_MID),
]
tb_lines(slide, branches_fa, 0.35, 4.28, 5.8, 2.8, font=FONT_MAIN, size=12)
# Right panel - diagram image
if img_face:
img_face_bytes = img_face
slide.shapes.add_picture(img_face_bytes, Inches(6.5), Inches(0.95), width=Inches(6.6))
# ─────────────────────────────────────────────────────────────────────────────
# SLIDE 4 ─ LINGUAL ARTERY
# ─────────────────────────────────────────────────────────────────────────────
slide = prs.slides.add_slide(blank)
set_bg(slide)
rect(slide, 0, 0, 13.333, 0.08, C_ACCENT)
rect(slide, 0, 0.08, 13.333, 0.75, C_DARK_CARD)
tb(slide, "Lingual Artery", 0.3, 0.1, 6.0, 0.65,
font=FONT_TITLE, size=26, bold=True, color=C_ACCENT)
tb(slide, "ARTERIAL SUPPLY", 8.0, 0.1, 5.0, 0.65,
font=FONT_TITLE, size=13, bold=True, color=C_MID, align=PP_ALIGN.RIGHT)
# Left col
rect(slide, 0.2, 1.0, 5.8, 6.3, C_DARK_CARD)
pill(slide, 0.3, 1.1, 2.0, 0.38, C_ACCENT)
tb(slide, "ORIGIN & COURSE", 0.3, 1.13, 2.0, 0.32,
font=FONT_MAIN, size=12, bold=True, color=C_WHITE, align=PP_ALIGN.CENTER)
la_course = [
("Origin: ECA at tip of greater horn of hyoid bone", 0, True, C_LIGHT),
("Forms upward loop, then passes deep to hyoglossus", 0, False, C_LIGHT),
("Enters floor of oral cavity via oropharyngeal triangle", 0, False, C_LIGHT),
("(bounded by mylohyoid + sup. + mid. constrictors)", 0, False, C_MID),
("Travels between hyoglossus and genioglossus", 0, False, C_LIGHT),
("Anastomoses with fellow at tongue apex (bilateral)", 0, True, C_GOLD),
]
tb_lines(slide, la_course, 0.35, 1.55, 5.5, 2.2, font=FONT_MAIN, size=13)
pill(slide, 0.3, 3.6, 1.8, 0.38, C_ACCENT)
tb(slide, "4 BRANCHES", 0.3, 3.63, 1.8, 0.32,
font=FONT_MAIN, size=12, bold=True, color=C_WHITE, align=PP_ALIGN.CENTER)
branch_cards = [
("Dorsal Lingual Aa. (2-3)", "Arise medial to hyoglossus\nPosterior dorsum, soft palate,\npalatoglossal arch, tonsil, epiglottis"),
("Sublingual Artery", "Anterior margin of hyoglossus\nSublingual gland, floor of mouth\nLingual foramen branch -> mandible"),
("Deep Lingual Artery", "Terminal branch\nInferior surface of tongue\nNear lingual frenum - visible through mucosa"),
]
bx = 0.28
for (bname, bdesc) in branch_cards:
rect(slide, bx, 4.1, 1.85, 2.7, RGBColor(0x1B, 0x32, 0x4A))
tb(slide, bname, bx+0.07, 4.15, 1.72, 0.42,
font=FONT_MAIN, size=12, bold=True, color=C_GOLD)
tb(slide, bdesc, bx+0.07, 4.6, 1.72, 2.1,
font=FONT_MAIN, size=11, color=C_LIGHT, wrap=True)
bx += 1.95
# Right col - image
if img_lv:
img_lv2 = fetch_image(IMAGE_LINGUAL_VEIN)
slide.shapes.add_picture(img_lv2, Inches(6.3), Inches(0.95), width=Inches(6.8))
# Additional supply to root
tb(slide, "Additional supply to tongue root: ascending palatine (facial a.) + ascending pharyngeal a. + superior laryngeal a. (epiglottic region)",
0.28, 7.05, 12.8, 0.4, font=FONT_MAIN, size=11, color=C_MID, italic=True)
# ─────────────────────────────────────────────────────────────────────────────
# SLIDE 5 ─ MAXILLARY ARTERY
# ─────────────────────────────────────────────────────────────────────────────
slide = prs.slides.add_slide(blank)
set_bg(slide)
rect(slide, 0, 0, 13.333, 0.08, C_ACCENT)
rect(slide, 0, 0.08, 13.333, 0.75, C_DARK_CARD)
tb(slide, "Maxillary Artery", 0.3, 0.1, 7.0, 0.65,
font=FONT_TITLE, size=26, bold=True, color=C_ACCENT)
tb(slide, "ARTERIAL SUPPLY | 3 PARTS", 7.0, 0.1, 6.0, 0.65,
font=FONT_TITLE, size=13, bold=True, color=C_MID, align=PP_ALIGN.RIGHT)
# 3 parts as columns
part_data = [
("PART 1\nMandibular",
RGBColor(0x8B, 0x20, 0x20),
["Inferior Alveolar Artery:",
" Into mandibular foramen",
" Mandibular molars + premolars",
" Splits into:",
" Incisive -> mandibular incisors",
" Mental -> chin + lower lip",
" Mylohyoid branch (before foramen)",
"",
"Between neck of mandible",
"and sphenomandibular ligament"]),
("PART 2\nPterygoid",
RGBColor(0x7A, 0x36, 0x10),
["Buccal Artery:",
" With buccal nerve on buccinator",
" Skin, muscle, oral mucosa of cheek",
" Buccal gingiva (lower posterior)",
"",
"Masseteric, Pterygoid,",
"Deep Temporal arteries",
"(muscles of mastication)",
"",
"Related to lateral pterygoid muscle"]),
("PART 3\nPterygopalatine",
RGBColor(0x1E, 0x5C, 0x3A),
["Greater Palatine Artery:",
" Hard palate (primary supply)",
" Palatal gingiva of maxillary teeth",
" -> Lesser palatine aa.",
" Soft palate + tonsil",
"Nasopalatine (sphenopalatine):",
" Anterior palate (incisors + canines)",
" via incisive canal",
"Superior Alveolar Arteries:",
" Post/Mid/Ant -> upper teeth plexus",
"Infra-orbital Artery:",
" Upper lip, lower eyelid, cheek"]),
]
px = 0.2
for (title, col, lines) in part_data:
rect(slide, px, 1.0, 4.2, 6.3, C_DARK_CARD)
rect(slide, px, 1.0, 4.2, 0.62, col)
tb(slide, title, px+0.1, 1.03, 4.0, 0.56,
font=FONT_TITLE, size=14, bold=True, color=C_WHITE, align=PP_ALIGN.CENTER)
y = 1.72
for line in lines:
is_sub = line.startswith(" ")
is_ind = line.startswith(" ")
is_bold_h = not is_sub and not is_ind and line != "" and not line.startswith("(")
col_l = C_GOLD if is_bold_h and ":" in line else (C_MID if line == "" else C_LIGHT)
sz = 11 if is_sub else (12 if is_ind else 12)
tb(slide, line, px+0.12, y, 3.9, 0.35,
font=FONT_MAIN, size=sz, bold=(is_bold_h and ":" in line), color=col_l)
y += 0.32
px += 4.35
# ─────────────────────────────────────────────────────────────────────────────
# SLIDE 6 ─ ARTERIAL SUMMARY TABLE
# ─────────────────────────────────────────────────────────────────────────────
slide = prs.slides.add_slide(blank)
set_bg(slide)
rect(slide, 0, 0, 13.333, 0.08, C_ACCENT)
rect(slide, 0, 0.08, 13.333, 0.75, C_DARK_CARD)
tb(slide, "Arterial Supply - Regional Summary", 0.3, 0.1, 10.0, 0.65,
font=FONT_TITLE, size=24, bold=True, color=C_ACCENT)
headers = ["Region", "Primary Artery", "Source"]
rows = [
("Upper lip", "Superior labial a. (facial a.)", "ECA"),
("Lower lip", "Inferior labial + mental a.", "ECA"),
("Tongue - body/apex", "Deep lingual a.", "Lingual a. (ECA)"),
("Tongue - dorsum/post", "Dorsal lingual aa.", "Lingual a. (ECA)"),
("Floor of mouth", "Sublingual a.", "Lingual a. (ECA)"),
("Hard palate (post.)", "Greater palatine a.", "Maxillary a. (ECA)"),
("Hard palate (ant.)", "Nasopalatine / sphenopalatine a.", "Maxillary a. (ECA)"),
("Soft palate", "Lesser palatine + ascending palatine", "Maxillary + Facial (ECA)"),
("Upper teeth", "Post./Mid./Ant. superior alveolar aa.","Maxillary a. (ECA)"),
("Lower teeth", "Inferior alveolar a. (incisive br.)", "Maxillary a. (ECA)"),
("Cheek / buccal mucosa","Buccal a.", "Maxillary a. (ECA)"),
("Chin", "Mental a.", "Inf. alveolar -> Maxillary"),
]
col_widths = [3.4, 5.5, 3.8]
col_starts = [0.2, 3.65, 9.2]
# Header row
hy = 0.97
for i, (hdr, cw, cs) in enumerate(zip(headers, col_widths, col_starts)):
rect(slide, cs, hy, cw-0.05, 0.42, C_ACCENT)
tb(slide, hdr, cs+0.1, hy+0.05, cw-0.15, 0.32,
font=FONT_MAIN, size=13, bold=True, color=C_WHITE, align=PP_ALIGN.LEFT)
ry = 1.42
for ri, row in enumerate(rows):
bg_col = C_DARK_CARD if ri % 2 == 0 else RGBColor(0x12, 0x22, 0x35)
rect(slide, 0.2, ry, 12.93, 0.40, bg_col)
for j, (val, cw, cs) in enumerate(zip(row, col_widths, col_starts)):
txt_col = C_GOLD if j == 1 else C_LIGHT
tb(slide, val, cs+0.1, ry+0.04, cw-0.15, 0.34,
font=FONT_MAIN, size=12, color=txt_col)
ry += 0.40
# ─────────────────────────────────────────────────────────────────────────────
# SLIDE 7 ─ VENOUS DRAINAGE TONGUE + LIPS
# ─────────────────────────────────────────────────────────────────────────────
slide = prs.slides.add_slide(blank)
set_bg(slide)
rect(slide, 0, 0, 13.333, 0.08, C_VENOUS)
rect(slide, 0, 0.08, 13.333, 0.75, C_DARK_CARD)
tb(slide, "Venous Drainage of the Oral Cavity", 0.3, 0.1, 10.0, 0.65,
font=FONT_TITLE, size=24, bold=True, color=C_VENOUS)
tb(slide, "VENOUS SUPPLY", 10.0, 0.1, 3.0, 0.65,
font=FONT_TITLE, size=13, bold=True, color=C_MID, align=PP_ALIGN.RIGHT)
# Left: tongue veins
rect(slide, 0.2, 1.0, 6.2, 5.9, C_DARK_CARD)
pill(slide, 0.3, 1.08, 2.5, 0.38, C_VENOUS)
tb(slide, "VEINS OF THE TONGUE", 0.3, 1.11, 2.5, 0.32,
font=FONT_MAIN, size=12, bold=True, color=C_WHITE, align=PP_ALIGN.CENTER)
tongue_veins = [
("Deep Lingual Vein", True, C_GOLD),
("Begins near apex, runs along ventral surface", False, C_LIGHT),
("Visible through mucosa beside frenulum", False, C_LIGHT),
("Travels with hypoglossal nerve [XII]", False, C_LIGHT),
("Joins sublingual vein from sublingual gland", False, C_LIGHT),
("-> Forms vena comitans nervi hypoglossi", False, C_MID),
("Drains to: lingual / facial / IJV", False, C_VENOUS),
("", False, C_MID),
("Dorsal Lingual Veins", True, C_GOLD),
("Drain dorsum + lateral sides of tongue", False, C_LIGHT),
("Follow lingual artery (hyoglossus / genioglossus)", False, C_LIGHT),
("Drain into IJV at level of hyoid bone", False, C_VENOUS),
]
y = 1.55
for (t, b, c) in tongue_veins:
tb(slide, t, 0.35, y, 5.9, 0.35, font=FONT_MAIN, size=12, bold=b, color=c)
y += 0.33
# Right: lips / cheek / palate / gingiva
rect(slide, 6.7, 1.0, 6.4, 5.9, C_DARK_CARD)
pill(slide, 6.8, 1.08, 3.2, 0.38, C_VENOUS)
tb(slide, "LIPS, CHEEK, PALATE, TEETH", 6.8, 1.11, 3.2, 0.32,
font=FONT_MAIN, size=12, bold=True, color=C_WHITE, align=PP_ALIGN.CENTER)
other_veins = [
("Lips", True, C_GOLD),
("Superior + inferior labial veins -> Facial vein", False, C_LIGHT),
("Facial vein drains to IJV in the neck", False, C_LIGHT),
("Common facial vein also receives retromandibular vein", False, C_MID),
("", False, None),
("Cheek", True, C_GOLD),
("Buccal vein -> Pterygoid venous plexus", False, C_LIGHT),
("", False, None),
("Hard Palate", True, C_GOLD),
("Palatine veins -> Pterygoid plexus", False, C_LIGHT),
("", False, None),
("Upper Teeth", True, C_GOLD),
("Ant. superior alveolar veins -> Facial vein", False, C_LIGHT),
("Post. superior alveolar veins -> Pterygoid plexus", False, C_LIGHT),
("", False, None),
("Lower Teeth", True, C_GOLD),
("Inf. alveolar veins: mental foramen -> Facial vein", False, C_LIGHT),
("OR mandibular foramen -> Pterygoid plexus", False, C_LIGHT),
("", False, None),
("Gingiva", True, C_GOLD),
("Buccal/lingual/palatine veins -> Pterygoid plexus", False, C_LIGHT),
("Lingual veins -> IJV (direct)", False, C_MID),
]
y = 1.55
for (t, b, c) in other_veins:
if c:
tb(slide, t, 6.85, y, 6.0, 0.33, font=FONT_MAIN, size=12, bold=b, color=c)
y += 0.28
# ─────────────────────────────────────────────────────────────────────────────
# SLIDE 8 ─ PTERYGOID PLEXUS (clinical)
# ─────────────────────────────────────────────────────────────────────────────
slide = prs.slides.add_slide(blank)
set_bg(slide)
rect(slide, 0, 0, 13.333, 0.08, C_VENOUS)
rect(slide, 0, 0.08, 13.333, 0.75, C_DARK_CARD)
tb(slide, "Pterygoid Venous Plexus", 0.3, 0.1, 8.0, 0.65,
font=FONT_TITLE, size=26, bold=True, color=C_VENOUS)
tb(slide, "VENOUS SUPPLY", 10.5, 0.1, 2.6, 0.65,
font=FONT_TITLE, size=13, bold=True, color=C_MID, align=PP_ALIGN.RIGHT)
# Main description
rect(slide, 0.2, 1.0, 8.3, 5.0, C_DARK_CARD)
pill(slide, 0.3, 1.08, 2.8, 0.38, C_VENOUS)
tb(slide, "LOCATION & TRIBUTARIES", 0.3, 1.11, 2.8, 0.32,
font=FONT_MAIN, size=12, bold=True, color=C_WHITE, align=PP_ALIGN.CENTER)
plexus_info = [
("Located between medial and lateral pterygoid muscles", False, C_LIGHT),
("Also between lateral pterygoid and temporalis", False, C_LIGHT),
("", False, None),
("Receives drainage from:", True, C_GOLD),
(" Nasal cavity, oral cavity (roof + lateral wall)", False, C_LIGHT),
(" All teeth", False, C_LIGHT),
(" Muscles of infratemporal fossa", False, C_LIGHT),
(" Paranasal sinuses + nasopharynx", False, C_LIGHT),
(" Inferior ophthalmic vein (via inf. orbital fissure)", False, C_LIGHT),
("", False, None),
("Connects posteriorly to maxillary vein", False, C_LIGHT),
("-> retromandibular vein -> IJV", False, C_MID),
("Connects anteriorly to facial vein", False, C_LIGHT),
("(via deep facial vein in the cheek)", False, C_MID),
("Connects superiorly to cavernous sinus", False, C_LIGHT),
("(via emissary veins - no valves!)", False, C_MID),
]
y = 1.55
for (t, b, c) in plexus_info:
if c:
tb(slide, t, 0.35, y, 8.0, 0.33, font=FONT_MAIN, size=13, bold=b, color=c)
y += 0.33
# Clinical alert box
rect(slide, 0.2, 6.1, 12.9, 1.2, RGBColor(0x3A, 0x10, 0x10))
rect(slide, 0.2, 6.1, 0.18, 1.2, C_ACCENT)
tb(slide, "CLINICAL: 'Danger Area of the Face'", 0.5, 6.15, 8.0, 0.42,
font=FONT_MAIN, size=14, bold=True, color=C_ACCENT)
tb(slide, "Because the pterygoid plexus connects to the cavernous sinus via VALVELESS emissary veins, "
"infections above the mouth can spread intracranially. Inadvertent injection into the plexus during "
"dental anesthesia can backflow intracranially.",
0.5, 6.58, 12.5, 0.65, font=FONT_MAIN, size=12, color=C_LIGHT, wrap=True)
# Right: summary table
rect(slide, 8.8, 1.0, 4.3, 5.0, C_DARK_CARD)
tb(slide, "VENOUS DRAINAGE SUMMARY", 8.9, 1.05, 4.1, 0.4,
font=FONT_MAIN, size=12, bold=True, color=C_VENOUS, align=PP_ALIGN.CENTER)
summary_rows = [
("Tongue", "Lingual veins / IJV"),
("Lips", "Facial vein -> IJV"),
("Cheek", "Buccal v. -> Pterygoid plexus"),
("Palate", "Pterygoid plexus"),
("Upper teeth", "Facial v. / Pterygoid plexus"),
("Lower teeth", "Facial v. / Pterygoid plexus"),
("Gingiva", "Pterygoid plexus (lingual->IJV)"),
]
ry2 = 1.52
for (region, drain) in summary_rows:
tb(slide, region, 8.85, ry2, 1.9, 0.38, font=FONT_MAIN, size=11, bold=True, color=C_GOLD)
tb(slide, drain, 10.75, ry2, 2.2, 0.38, font=FONT_MAIN, size=11, color=C_LIGHT)
ry2 += 0.42
# ─────────────────────────────────────────────────────────────────────────────
# SLIDE 9 ─ LYMPH OVERVIEW + TONGUE
# ─────────────────────────────────────────────────────────────────────────────
slide = prs.slides.add_slide(blank)
set_bg(slide)
rect(slide, 0, 0, 13.333, 0.08, C_LYMPH)
rect(slide, 0, 0.08, 13.333, 0.75, C_DARK_CARD)
tb(slide, "Lymphatic Drainage - Overview & Tongue", 0.3, 0.1, 10.0, 0.65,
font=FONT_TITLE, size=24, bold=True, color=C_LYMPH)
tb(slide, "LYMPHATIC DRAINAGE", 10.0, 0.1, 3.0, 0.65,
font=FONT_TITLE, size=13, bold=True, color=C_MID, align=PP_ALIGN.RIGHT)
# Left: overview card
rect(slide, 0.2, 1.0, 4.2, 6.0, C_DARK_CARD)
pill(slide, 0.3, 1.08, 2.5, 0.38, C_LYMPH)
tb(slide, "PRINCIPAL NODE GROUPS", 0.3, 1.11, 2.5, 0.32,
font=FONT_MAIN, size=11, bold=True, color=C_WHITE, align=PP_ALIGN.CENTER)
node_groups = [
("Level IA - Submental", True, C_GOLD),
("Tip of tongue, anterior floor of mouth,", False, C_LIGHT),
("midline lower lip, mandibular incisors", False, C_LIGHT),
("", False, None),
("Level IB - Submandibular", True, C_GOLD),
("Lateral tongue, lateral floor of mouth,", False, C_LIGHT),
("cheek, upper lip, lateral lower lip, most teeth", False, C_LIGHT),
("", False, None),
("Level IIA - Jugulodigastric", True, C_GOLD),
("Post./lateral tongue, palate, gingiva,", False, C_LIGHT),
("molars; secondary node for whole oral cavity", False, C_LIGHT),
("", False, None),
("Level III - Jugulo-omohyoid", True, C_GOLD),
("Tongue tip (direct), secondary from I+II", False, C_LIGHT),
("", False, None),
("Retropharyngeal nodes", True, C_GOLD),
("Posterior oral tongue + soft palate", False, C_LIGHT),
]
y = 1.55
for (t, b, c) in node_groups:
if c:
tb(slide, t, 0.35, y, 3.9, 0.33, font=FONT_MAIN, size=12, bold=b, color=c)
y += 0.32
# Right: tongue lymphatics
rect(slide, 4.65, 1.0, 8.4, 6.0, C_DARK_CARD)
pill(slide, 4.75, 1.08, 3.2, 0.38, C_LYMPH)
tb(slide, "LYMPHATICS OF THE TONGUE (3 Zones)", 4.75, 1.11, 3.2, 0.32,
font=FONT_MAIN, size=12, bold=True, color=C_WHITE, align=PP_ALIGN.CENTER)
tongue_lymph = [
("Zone 1: MARGINAL VESSELS (Tip + Lateral margins)", True, C_GOLD),
("Tip/frenum area: submental (IA) + jugulo-omohyoid (III)", False, C_LIGHT),
("Cross-midline drainage to CONTRALATERAL nodes", False, C_ACCENT),
("Lateral margin: submandibular (IB)", False, C_LIGHT),
("Also jugulodigastric (IIA) / jugulo-omohyoid (III)", False, C_LIGHT),
("Posterior lateral margin: traverses pharyngeal wall to IIA", False, C_MID),
("", False, None),
("Zone 2: CENTRAL VESSELS (Body of tongue)", True, C_GOLD),
("Drains BILATERALLY to deep cervical nodes", False, C_ACCENT),
("Especially IIA (jugulodigastric) + III (jugulo-omohyoid)", False, C_LIGHT),
("Some pierce mylohyoid to submandibular nodes (IB)", False, C_LIGHT),
("", False, None),
("Zone 3: DORSAL VESSELS (Circumvallate + posterior)", True, C_GOLD),
("Run posteroinferiorly", False, C_LIGHT),
("Median plane: BOTH sides -> IIA + III", False, C_ACCENT),
("", False, None),
("Anterior (oral) tongue: Levels I, II, III, IV", True, C_LYMPH),
("Posterior (oropharyngeal) tongue: bilateral deep cervical + retropharyngeal", True, C_LYMPH),
]
y = 1.55
for (t, b, c) in tongue_lymph:
if c:
tb(slide, t, 4.8, y, 8.0, 0.34, font=FONT_MAIN, size=12, bold=b, color=c)
y += 0.31
# ─────────────────────────────────────────────────────────────────────────────
# SLIDE 10 ─ LYMPH: REGION BY REGION
# ─────────────────────────────────────────────────────────────────────────────
slide = prs.slides.add_slide(blank)
set_bg(slide)
rect(slide, 0, 0, 13.333, 0.08, C_LYMPH)
rect(slide, 0, 0.08, 13.333, 0.75, C_DARK_CARD)
tb(slide, "Lymphatic Drainage - Region by Region", 0.3, 0.1, 10.0, 0.65,
font=FONT_TITLE, size=24, bold=True, color=C_LYMPH)
regions = [
("LIPS", [
("Upper lip + cheek", "Submandibular (Level IB)"),
("Lateral lower lip", "Submandibular (Level IB)"),
("Central/midline lower lip", "Submental (Level IA) - bilateral"),
]),
("FLOOR OF MOUTH", [
("Lateral floor of mouth", "Ipsilateral submandibular (IB) -> deep cervical"),
("Anterior floor of mouth", "Submental (IA) -> BOTH left + right deep cervical"),
("Lingual nodes (above mylohyoid)", "Clinically important for floor-of-mouth tumours"),
]),
("TEETH & GINGIVA", [
("Most teeth", "Submandibular (IB) - ipsilateral"),
("Mandibular incisors", "Submental (IA)"),
("Mandibular molars (occasionally)", "Jugulodigastric (IIA) directly"),
("Lingual + palatal gingiva", "Jugulodigastric (IIA), or via submandibular"),
]),
("PALATE", [
("Bulk of palate", "Jugulodigastric (IIA) directly"),
("Soft palate + tonsil", "Jugulodigastric (IIA) + retropharyngeal nodes"),
("Palatal gingiva", "Jugulodigastric (IIA) or via submandibular"),
]),
]
col_x = [0.2, 4.55, 9.0]
col_w = 4.25
for ci, (title, rows) in enumerate(regions):
x = col_x[ci] if ci < 3 else col_x[2]
# For 4 regions, do 2x2
if ci == 0:
x = 0.2; ry = 1.0
elif ci == 1:
x = 4.55; ry = 1.0
elif ci == 2:
x = 0.2; ry = 4.1
else:
x = 4.55; ry = 4.1
rect(slide, x, ry, 4.25, 3.2, C_DARK_CARD)
rect(slide, x, ry, 4.25, 0.45, C_LYMPH)
tb(slide, title, x+0.1, ry+0.06, 4.0, 0.35,
font=FONT_MAIN, size=13, bold=True, color=C_WHITE, align=PP_ALIGN.CENTER)
iy = ry + 0.53
for (region2, nodes) in rows:
tb(slide, region2, x+0.15, iy, 4.0, 0.32, font=FONT_MAIN, size=12, bold=True, color=C_GOLD)
iy += 0.3
tb(slide, nodes, x+0.25, iy, 3.9, 0.34, font=FONT_MAIN, size=11, color=C_LIGHT, wrap=True)
iy += 0.38
# Right column: neck level table
rect(slide, 9.0, 1.0, 4.1, 6.3, C_DARK_CARD)
tb(slide, "NECK LEVEL REFERENCE", 9.05, 1.05, 3.9, 0.42,
font=FONT_MAIN, size=12, bold=True, color=C_LYMPH, align=PP_ALIGN.CENTER)
level_data = [
("Level IA", "Submental", "Tongue tip, ant. floor of mouth, midline lower lip"),
("Level IB", "Submandibular", "Lat. tongue, floor mouth, cheek, upper lip, teeth"),
("Level IIA", "Jugulodigastric", "Post. tongue, palate, gingiva, whole oral cavity 2°"),
("Level III", "Jugulo-omohyoid", "Tongue tip (direct), 2° from I+II"),
("Level IV", "Lower jugular", "Tongue (secondary)"),
("Retropharyngeal", "–", "Post. oral tongue, soft palate"),
]
ly = 1.55
for (lvl, name, desc) in level_data:
rect(slide, 9.05, ly, 3.9, 0.9, RGBColor(0x12, 0x22, 0x35))
tb(slide, lvl, 9.1, ly+0.03, 1.2, 0.3, font=FONT_MAIN, size=11, bold=True, color=C_GOLD)
tb(slide, name, 10.35, ly+0.03, 2.55, 0.3, font=FONT_MAIN, size=11, bold=False, color=C_LYMPH)
tb(slide, desc, 9.1, ly+0.33, 3.8, 0.52, font=FONT_MAIN, size=10, color=C_MID, wrap=True)
ly += 0.95
# ─────────────────────────────────────────────────────────────────────────────
# SLIDE 11 ─ CLINICAL CORRELATIONS
# ─────────────────────────────────────────────────────────────────────────────
slide = prs.slides.add_slide(blank)
set_bg(slide)
rect(slide, 0, 0, 13.333, 0.08, C_GOLD)
rect(slide, 0, 0.08, 13.333, 0.75, C_DARK_CARD)
tb(slide, "Clinical Correlations", 0.3, 0.1, 10.0, 0.65,
font=FONT_TITLE, size=26, bold=True, color=C_GOLD)
clinical_points = [
(C_ACCENT, "Mandibular Block Anesthesia",
"Targets inferior alveolar nerve + artery at mandibular foramen; anesthetizes mandibular molars, premolars, and lower lip"),
(C_ACCENT, "Floor-of-Mouth Hemorrhage",
"Sublingual artery branch enters through lingual foramen on posterior mandibular symphysis; "
"anterior mandibular implant placement can cause life-threatening bleeding in the floor of the mouth"),
(C_ACCENT, "Tongue Vascularity",
"Deep lingual arteries anastomose at the tongue tip. Lingual artery ligation distal to the origin "
"of the dorsal lingual arteries is safer for tongue-base resections"),
(C_VENOUS, "Danger Area of Face / Dental Anesthesia",
"Valveless pterygoid plexus connects to cavernous sinus; facial infections above the mouth + "
"inadvertent intravenous injection during inferior alveolar block can spread intracranially"),
(C_VENOUS, "Thyroglossofacial Confluence (Surgical Anatomy)",
"During neck dissection / hypoglossal nerve dissection, a venous confluence of thyroid, face, and tongue veins "
"(venae comitantes nervi hypoglossi) empties into the IJV - division of the common facial vein improves access"),
(C_LYMPH, "Oral Cavity Cancer - Bilateral Lymph Node Risk",
"Midline structures (tongue body, floor of mouth, lower lip) drain bilaterally; "
"any asymmetrically enlarged node in bilateral drainage zones is suspicious for metastasis"),
(C_LYMPH, "Supraomohyoid Neck Dissection (SND I-III)",
"Standard for oral cavity cancer; removes submental (IA), submandibular (IB), and upper jugular (IIA) nodes; "
"submandibular gland is included in the Level IB specimen"),
(C_LYMPH, "Lingual Lymph Nodes in Floor of Mouth",
"Small nodes located above the mylohyoid in the floor of mouth; clinically important when managing "
"floor-of-mouth carcinoma (staging and surgical planning)"),
(C_LYMPH, "Posterior Epistaxis Anastomosis",
"Greater palatine artery anastomoses with sphenopalatine (nasopalatine) via incisive canal; "
"posterior epistaxis may respond to descending palatine artery ligation"),
]
cx = 0.2
row_h = 0.72
cols_n = 2
col_w2 = 6.3
for i, (color, title, desc) in enumerate(clinical_points):
ci = i % cols_n
ri = i // cols_n
x = 0.2 + ci * 6.55
y = 1.02 + ri * row_h
rect(slide, x, y, col_w2, row_h - 0.06, C_DARK_CARD)
rect(slide, x, y, 0.18, row_h - 0.06, color)
tb(slide, title, x + 0.28, y + 0.04, col_w2 - 0.35, 0.28,
font=FONT_MAIN, size=12, bold=True, color=color)
tb(slide, desc, x + 0.28, y + 0.32, col_w2 - 0.35, 0.34,
font=FONT_MAIN, size=10, color=C_LIGHT, wrap=True)
# ─────────────────────────────────────────────────────────────────────────────
# SLIDE 12 ─ KEY ANASTOMOSES
# ─────────────────────────────────────────────────────────────────────────────
slide = prs.slides.add_slide(blank)
set_bg(slide)
rect(slide, 0, 0, 13.333, 0.08, C_GOLD)
rect(slide, 0, 0.08, 13.333, 0.75, C_DARK_CARD)
tb(slide, "Key Anastomoses & Take-Home Summary", 0.3, 0.1, 10.0, 0.65,
font=FONT_TITLE, size=24, bold=True, color=C_GOLD)
anastomoses = [
("1", C_ACCENT, "Labial arteries", "Superior + inferior labial branches anastomose across the midline, connecting both external carotid circulations"),
("2", C_ACCENT, "Deep lingual arteries", "Anastomose at the tongue tip bilaterally"),
("3", C_ACCENT, "Greater palatine + sphenopalatine", "Anastomose via the incisive canal"),
("4", C_ACCENT, "Lesser palatine + ascending palatine", "Anastomose in the soft palate (facial a. + maxillary a. connection)"),
("5", C_ACCENT, "Superior laryngeal + dorsal lingual", "Anastomose near the valleculae (epiglottic region)"),
]
ay = 1.08
for (num, col, title, desc) in anastomoses:
rect(slide, 0.2, ay, 12.9, 0.75, C_DARK_CARD)
pill(slide, 0.25, ay+0.17, 0.42, 0.42, col)
tb(slide, num, 0.25, ay+0.2, 0.42, 0.35, font=FONT_MAIN, size=14, bold=True,
color=C_WHITE, align=PP_ALIGN.CENTER)
tb(slide, title, 0.78, ay+0.07, 3.0, 0.32, font=FONT_MAIN, size=13, bold=True, color=col)
tb(slide, desc, 0.78, ay+0.38, 12.0, 0.3, font=FONT_MAIN, size=12, color=C_LIGHT)
ay += 0.82
# Take-home summary box
rect(slide, 0.2, 5.2, 12.9, 2.1, RGBColor(0x12, 0x22, 0x20))
rect(slide, 0.2, 5.2, 0.2, 2.1, C_LYMPH)
tb(slide, "TAKE-HOME SUMMARY", 0.55, 5.25, 5.0, 0.4,
font=FONT_MAIN, size=14, bold=True, color=C_LYMPH)
summary_lines = [
"ARTERIAL: Oral cavity receives blood from ECA via Facial (lips + soft palate), Lingual (tongue + floor of mouth), and Maxillary (teeth, palate, cheek) arteries",
"VENOUS: Drains via Facial vein and Pterygoid plexus -> IJV. Pterygoid plexus is valveless and connects to cavernous sinus (danger area)",
"LYMPHATIC: Submental (IA) for midline, Submandibular (IB) for lateral structures, Jugulodigastric (IIA) for posterior/palate. Midline = bilateral drainage. SND I-III for oral cavity cancer",
]
sy = 5.68
for line in summary_lines:
col_l = C_ACCENT if "ARTERIAL" in line else (C_VENOUS if "VENOUS" in line else C_LYMPH)
tb(slide, line, 0.55, sy, 12.5, 0.4, font=FONT_MAIN, size=12, color=C_LIGHT, wrap=True)
sy += 0.42
# ─────────────────────────────────────────────────────────────────────────────
# SAVE
# ─────────────────────────────────────────────────────────────────────────────
out_path = "/tmp/workspace/oral-cavity-pptx/Oral_Cavity_Vascular_Lymphatic_Anatomy.pptx"
buf = BytesIO()
prs.save(buf)
with open(out_path, "wb") as f:
f.write(buf.getvalue())
print(f"Saved: {out_path}")