~/cervical-rib-ppt/build_ppt.py
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
# ── Colour palette ──────────────────────────────────────────────────────────
DARK_BLUE = RGBColor(0x1A, 0x35, 0x5E) # deep navy – title bar / accents
MID_BLUE = RGBColor(0x2E, 0x6D, 0xA4) # medium blue – section headers
LIGHT_BLUE = RGBColor(0xD6, 0xE8, 0xF7) # pale blue – content bg
ACCENT = RGBColor(0x00, 0xB0, 0xF0) # sky-blue – divider line / bullets
WHITE = RGBColor(0xFF, 0xFF, 0xFF)
DARK_TEXT = RGBColor(0x1A, 0x1A, 0x2E)
ORANGE = RGBColor(0xE8, 0x6A, 0x1A) # highlight
LIGHT_GREY = RGBColor(0xF4, 0xF7, 0xFB)
W = Inches(13.333)
H = Inches(7.5)
prs = Presentation()
prs.slide_width = W
prs.slide_height = H
blank = prs.slide_layouts[6]
# ── Helper utilities ──────────────────────────────────────────────────────────
def add_rect(slide, x, y, w, h, fill_rgb, line_rgb=None, line_pt=0):
shp = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, x, y, w, h)
shp.fill.solid(); shp.fill.fore_color.rgb = fill_rgb
if line_rgb:
shp.line.color.rgb = line_rgb; shp.line.width = Pt(line_pt)
else:
shp.line.fill.background()
shp.shadow.inherit = False
return shp
def add_tb(slide, x, y, w, h, text, font_size, bold=False, color=DARK_TEXT,
align=PP_ALIGN.LEFT, italic=False, wrap=True, font_name="Calibri"):
tb = slide.shapes.add_textbox(x, y, w, h)
tf = tb.text_frame; tf.word_wrap = wrap
tf.margin_left = tf.margin_right = tf.margin_top = tf.margin_bottom = 0
p = tf.paragraphs[0]; p.alignment = align
r = p.add_run(); r.text = text
r.font.name = font_name; r.font.size = Pt(font_size)
r.font.bold = bold; r.font.italic = italic
r.font.color.rgb = color
return tb
def add_para(tf, text, font_size, bold=False, color=DARK_TEXT,
align=PP_ALIGN.LEFT, italic=False, level=0, font_name="Calibri"):
p = tf.add_paragraph(); p.alignment = align; p.level = level
r = p.add_run(); r.text = text
r.font.name = font_name; r.font.size = Pt(font_size)
r.font.bold = bold; r.font.italic = italic
r.font.color.rgb = color
return p
def std_header(slide, title_text, subtitle_text=""):
"""Top navy bar + white title + divider line."""
add_rect(slide, 0, 0, W, Inches(1.15), DARK_BLUE)
add_tb(slide, Inches(0.4), Inches(0.1), Inches(12.5), Inches(0.85),
title_text, 32, bold=True, color=WHITE, align=PP_ALIGN.LEFT)
if subtitle_text:
add_tb(slide, Inches(0.4), Inches(0.95), Inches(12.5), Inches(0.3),
subtitle_text, 14, bold=False, color=ACCENT, align=PP_ALIGN.LEFT)
# accent line
ln = slide.shapes.add_connector(
__import__('pptx.enum.shapes', fromlist=['MSO_CONNECTOR']).MSO_CONNECTOR.STRAIGHT,
0, Inches(1.15), W, Inches(1.15))
ln.line.color.rgb = ACCENT; ln.line.width = Pt(3)
def bg(slide):
"""Light background."""
add_rect(slide, 0, 0, W, H, LIGHT_GREY)
def footer(slide, txt="Cervical Rib | Clinical & Physiotherapy Guide"):
add_rect(slide, 0, Inches(7.15), W, Inches(0.35), DARK_BLUE)
add_tb(slide, Inches(0.3), Inches(7.18), Inches(12.7), Inches(0.28),
txt, 9, color=RGBColor(0xCC,0xDD,0xEE), align=PP_ALIGN.LEFT)
def bullet_box(slide, x, y, w, h, items, font_size=14, title=None, title_size=16):
"""Draws a rounded card with optional title + bullet items."""
card = slide.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE, x, y, w, h)
card.fill.solid(); card.fill.fore_color.rgb = WHITE
card.line.color.rgb = MID_BLUE; card.line.width = Pt(1)
card.shadow.inherit = False
# title bar inside card
if title:
tbar = slide.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE, x, y, w, Inches(0.38))
tbar.fill.solid(); tbar.fill.fore_color.rgb = MID_BLUE
tbar.line.fill.background(); tbar.shadow.inherit = False
add_tb(slide, x+Inches(0.1), y+Inches(0.04), w-Inches(0.2), Inches(0.3),
title, title_size, bold=True, color=WHITE)
cy = y + Inches(0.42)
else:
cy = y + Inches(0.1)
tb = slide.shapes.add_textbox(x+Inches(0.15), cy, w-Inches(0.3), h-(cy-y)-Inches(0.1))
tf = tb.text_frame; tf.word_wrap = True
tf.margin_left = tf.margin_right = tf.margin_top = tf.margin_bottom = 0
first = True
for item in items:
if first:
tf.paragraphs[0].alignment = PP_ALIGN.LEFT
r = tf.paragraphs[0].add_run()
r.text = item; r.font.name = "Calibri"; r.font.size = Pt(font_size)
r.font.color.rgb = DARK_TEXT; first = False
else:
add_para(tf, item, font_size)
# ═══════════════════════════════════════════════════════════════════════════════
# SLIDE 1 — TITLE
# ═══════════════════════════════════════════════════════════════════════════════
slide = prs.slides.add_slide(blank)
add_rect(slide, 0, 0, W, H, DARK_BLUE)
# diagonal accent strip
strip = slide.shapes.add_shape(MSO_SHAPE.PARALLELOGRAM, Inches(-1), Inches(2.5), Inches(15), Inches(2.5))
strip.fill.solid(); strip.fill.fore_color.rgb = MID_BLUE
strip.line.fill.background(); strip.shadow.inherit = False
add_tb(slide, Inches(0.8), Inches(1.2), Inches(11), Inches(1.2),
"CERVICAL RIB", 56, bold=True, color=WHITE, align=PP_ALIGN.LEFT, font_name="Calibri")
add_tb(slide, Inches(0.8), Inches(2.5), Inches(11), Inches(0.6),
"Thoracic Outlet Syndrome — Clinical & Physiotherapy Guide", 22,
color=ACCENT, align=PP_ALIGN.LEFT)
add_tb(slide, Inches(0.8), Inches(3.3), Inches(11), Inches(0.45),
"Introduction | Anatomy | Etiology | Clinical Features | Classification", 14,
color=RGBColor(0xCC,0xDD,0xEE), align=PP_ALIGN.LEFT)
add_tb(slide, Inches(0.8), Inches(3.7), Inches(11), Inches(0.45),
"Investigations | Treatment | Surgical Management | Physiotherapy", 14,
color=RGBColor(0xCC,0xDD,0xEE), align=PP_ALIGN.LEFT)
add_tb(slide, Inches(0.8), Inches(6.6), Inches(11), Inches(0.5),
"References: Gray's Anatomy | Bailey & Love | Harrison's 22E | S. Das | Sabiston | Jayant Joshi", 11,
color=RGBColor(0x88,0xAA,0xCC), align=PP_ALIGN.LEFT, italic=True)
# ═══════════════════════════════════════════════════════════════════════════════
# SLIDE 2 — INTRODUCTION & DEFINITION
# ═══════════════════════════════════════════════════════════════════════════════
slide = prs.slides.add_slide(blank)
bg(slide); std_header(slide, "Introduction & Definition"); footer(slide)
# Intro box
intro = [
"Cervical rib is a congenital skeletal anomaly where an extra rib arises from C7 vertebra.",
"Present in ~1% of the population; symptomatic in only ~10% of those.",
"Compresses neurovascular structures at the thoracic outlet → Thoracic Outlet Syndrome (TOS).",
"Bilateral in 50–80% of cases.",
"Coote first performed surgical excision in 1861.",
]
bullet_box(slide, Inches(0.4), Inches(1.3), Inches(6.2), Inches(2.7), intro,
font_size=14, title="Introduction", title_size=15)
# Definition box
defn = [
"A cervical rib is a supernumerary rib articulating with the 7th cervical vertebra (C7).",
"Represents persistence of the costal element of C7 that normally regresses in development.",
"A fibrous band often extends from the rib tip to rib I — invisible on X-ray but equally symptomatic.",
"\"A cervical rib is an accessory rib articulating with vertebra CVII; the anterior end attaches to the superior border of the anterior aspect of rib I.\" — Gray's Anatomy for Students",
]
bullet_box(slide, Inches(7.0), Inches(1.3), Inches(6.0), Inches(2.7), defn,
font_size=13, title="Definition", title_size=15)
# TOS definition
tos = [
"Thoracic Outlet Syndrome (TOS) = clinical complex of symptoms from compression of:",
" • Brachial plexus • Subclavian artery • Subclavian vein",
"at the thoracic outlet due to cervical rib or related anomalies.",
]
bullet_box(slide, Inches(0.4), Inches(4.2), Inches(12.5), Inches(2.5), tos,
font_size=14, title="Thoracic Outlet Syndrome (TOS)", title_size=15)
# ═══════════════════════════════════════════════════════════════════════════════
# SLIDE 3 — ANATOMY
# ═══════════════════════════════════════════════════════════════════════════════
slide = prs.slides.add_slide(blank)
bg(slide); std_header(slide, "Anatomy of the Thoracic Outlet"); footer(slide)
# Fetch image
IMG_URL = "https://cdn.orris.care/cdss_images/d500d28ff92d145b3215667d1059a402c5c6a3372212f9c5a84e913ba02edf9a.png"
try:
result = json.loads(subprocess.check_output(
["python", "/tmp/skills/shared/scripts/fetch_images.py", IMG_URL], timeout=30))
if result[0]["base64"]:
raw = base64.b64decode(result[0]["base64"].split(",", 1)[1])
slide.shapes.add_picture(BytesIO(raw), Inches(0.3), Inches(1.3), width=Inches(5.5))
add_tb(slide, Inches(0.3), Inches(5.7), Inches(5.5), Inches(0.4),
"THIEME Atlas: Thoracic outlet — scalene muscles, brachial plexus, subclavian vessels",
9, italic=True, color=RGBColor(0x55,0x55,0x55))
except Exception as e:
add_tb(slide, Inches(0.3), Inches(2.5), Inches(5.0), Inches(0.4),
f"[Image not loaded: {e}]", 10, italic=True)
# Three spaces table
spaces = [
("Interscalene Triangle", "Subclavian artery + Brachial plexus"),
("Costoclavicular Space", "Subclavian vein + artery + Brachial plexus"),
("Subcoracoid Space", "Neurovascular bundle (extreme abduction)"),
]
add_rect(slide, Inches(6.1), Inches(1.3), Inches(6.8), Inches(0.38), MID_BLUE)
add_tb(slide, Inches(6.1), Inches(1.32), Inches(6.8), Inches(0.35),
"Three Key Anatomic Spaces", 15, bold=True, color=WHITE)
row_colors = [WHITE, LIGHT_BLUE, WHITE]
for i, (space, struct) in enumerate(spaces):
ry = Inches(1.68) + i*Inches(0.48)
add_rect(slide, Inches(6.1), ry, Inches(6.8), Inches(0.46), row_colors[i])
add_tb(slide, Inches(6.15), ry+Inches(0.05), Inches(3.2), Inches(0.38),
space, 13, bold=True, color=DARK_BLUE)
add_tb(slide, Inches(9.4), ry+Inches(0.05), Inches(3.4), Inches(0.38),
struct, 12, color=DARK_TEXT)
# Key points
key = [
"Lower trunk of brachial plexus (C8, T1) — most vulnerable to cervical rib compression.",
"T1 nerve root is elevated → tension + friction over bony/fibrous cervical rib.",
"Subclavian artery is kinked/angulated → post-stenotic dilatation + thrombus formation.",
"Subclavian vein compressed in costoclavicular space → axillary vein thrombosis.",
]
bullet_box(slide, Inches(6.1), Inches(3.2), Inches(6.8), Inches(2.5), key,
font_size=13, title="Key Points", title_size=14)
# ═══════════════════════════════════════════════════════════════════════════════
# SLIDE 4 — ETIOLOGY
# ═══════════════════════════════════════════════════════════════════════════════
slide = prs.slides.add_slide(blank)
bg(slide); std_header(slide, "Etiology & Predisposing Factors"); footer(slide)
causes = [
("Congenital cervical rib", "Directly narrows interscalene triangle — primary cause"),
("Sagging shoulder girdle", "Increases plexus traction over rib — typical at puberty in women (S. Das)"),
("Neck trauma / whiplash", "Triggers symptoms in previously asymptomatic cervical rib"),
("Repetitive overhead activity", "Repeated microtrauma to compressed structures"),
("Anomalous scalene muscles", "Accessory scalene bands add additional compression"),
("Previous clavicle fracture", "Callus formation reduces costoclavicular space"),
("Poor posture", "Forward head + rounded shoulders narrows all three outlet spaces"),
("Fibrous band at C7", "Even without bony rib, fibromuscular band from C7 produces identical symptoms"),
]
col_w = Inches(6.2)
col_gap = Inches(0.4)
lx = Inches(0.4)
rx = lx + col_w + col_gap
for i, (cause, mech) in enumerate(causes):
col = 0 if i < 4 else 1
row = i if i < 4 else i - 4
x = lx if col == 0 else rx
y = Inches(1.4) + row * Inches(1.3)
card = slide.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE, x, y, col_w, Inches(1.15))
card.fill.solid(); card.fill.fore_color.rgb = WHITE
card.line.color.rgb = ACCENT; card.line.width = Pt(1.2)
card.shadow.inherit = False
# coloured left strip
strip2 = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, x, y, Inches(0.12), Inches(1.15))
strip2.fill.solid(); strip2.fill.fore_color.rgb = MID_BLUE
strip2.line.fill.background(); strip2.shadow.inherit = False
add_tb(slide, x+Inches(0.2), y+Inches(0.05), col_w-Inches(0.25), Inches(0.35),
cause, 14, bold=True, color=DARK_BLUE)
add_tb(slide, x+Inches(0.2), y+Inches(0.42), col_w-Inches(0.25), Inches(0.65),
mech, 12, color=DARK_TEXT, wrap=True)
# ═══════════════════════════════════════════════════════════════════════════════
# SLIDE 5 — CLINICAL FEATURES
# ═══════════════════════════════════════════════════════════════════════════════
slide = prs.slides.add_slide(blank)
bg(slide); std_header(slide, "Clinical Features (S. Das — Three Groups)"); footer(slide)
# X-ray image
XRAY_URL = "https://cdn.orris.care/cdss_images/d50bb3631e33bc89f650e331d71047c789f9e0cb0fb600176ff62d7df024b863.png"
try:
result2 = json.loads(subprocess.check_output(
["python", "/tmp/skills/shared/scripts/fetch_images.py", XRAY_URL], timeout=30))
if result2[0]["base64"]:
raw2 = base64.b64decode(result2[0]["base64"].split(",", 1)[1])
slide.shapes.add_picture(BytesIO(raw2), Inches(10.8), Inches(1.5), width=Inches(2.2))
add_tb(slide, Inches(10.8), Inches(4.2), Inches(2.2), Inches(0.4),
"Bilateral cervical ribs on AP X-ray", 9, italic=True, color=RGBColor(0x55,0x55,0x55))
except:
pass
groups = [
("(a) Local", [
"Hard, fixed lump in the lower posterior triangle of the neck",
"Local pain and tenderness in the supraclavicular fossa",
], MID_BLUE),
("(b) Neurogenic (Lower trunk C8/T1)", [
"Sensory: tingling, numbness, pain — medial forearm + hand (ulnar distribution)",
"Motor: weakness + wasting of thenar/hypothenar eminences and interossei",
"Claw hand in advanced cases (intrinsic paralysis)",
"Vasomotor: hyperhidrosis, digital gangrene (extreme)",
], DARK_BLUE),
("(c) Vascular", [
"Forearm pain worse with exercise (claudication)",
"Hand colder than opposite; pale on elevation, cyanotic when dependent",
"Feeble or absent radial pulse on affected side",
"Digital emboli — black areas on fingertips (subclavian artery thrombus)",
"Arm swelling + DVT (subclavian vein compression)",
], RGBColor(0x8B, 0x00, 0x00)),
]
gy = Inches(1.35)
for (grp_title, items, hdr_color) in groups:
bh = Inches(0.38 + len(items)*0.38)
card = slide.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE, Inches(0.35), gy, Inches(10.2), bh)
card.fill.solid(); card.fill.fore_color.rgb = WHITE
card.line.color.rgb = hdr_color; card.line.width = Pt(1.5)
card.shadow.inherit = False
tbar2 = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, Inches(0.35), gy, Inches(10.2), Inches(0.36))
tbar2.fill.solid(); tbar2.fill.fore_color.rgb = hdr_color
tbar2.line.fill.background(); tbar2.shadow.inherit = False
add_tb(slide, Inches(0.5), gy+Inches(0.04), Inches(10.0), Inches(0.3),
grp_title, 15, bold=True, color=WHITE)
tbx = slide.shapes.add_textbox(Inches(0.55), gy+Inches(0.4), Inches(9.9), bh-Inches(0.42))
tf2 = tbx.text_frame; tf2.word_wrap = True
tf2.margin_left = tf2.margin_right = tf2.margin_top = tf2.margin_bottom = 0
first2 = True
for it in items:
if first2:
tf2.paragraphs[0].alignment = PP_ALIGN.LEFT
r2 = tf2.paragraphs[0].add_run()
r2.text = " " + it; r2.font.name = "Calibri"; r2.font.size = Pt(13)
r2.font.color.rgb = DARK_TEXT; first2 = False
else:
p2 = tf2.add_paragraph(); p2.alignment = PP_ALIGN.LEFT
r3 = p2.add_run(); r3.text = " " + it
r3.font.name = "Calibri"; r3.font.size = Pt(13)
r3.font.color.rgb = DARK_TEXT
gy += bh + Inches(0.1)
# ═══════════════════════════════════════════════════════════════════════════════
# SLIDE 6 — CLASSIFICATION
# ═══════════════════════════════════════════════════════════════════════════════
slide = prs.slides.add_slide(blank)
bg(slide); std_header(slide, "Classification of Cervical Rib / TOS"); footer(slide)
# TOS by structure
add_rect(slide, Inches(0.3), Inches(1.3), Inches(6.0), Inches(0.38), MID_BLUE)
add_tb(slide, Inches(0.35), Inches(1.32), Inches(5.9), Inches(0.35),
"By Structure Compressed (Harrison's / Sabiston)", 14, bold=True, color=WHITE)
tos_rows = [
("Neurogenic TOS", "Brachial plexus", "~95%", "Motor/sensory deficits, pain, paresthesia"),
("Venous TOS", "Subclavian vein", "~3-4%", "Arm swelling, cyanosis, DVT"),
("Arterial TOS", "Subclavian artery", "~1-2%", "Ischemia, emboli, cold hand"),
("Disputed TOS", "Unclear", "~95% of diagnosed", "Chronic pain, no objective signs"),
]
col_hdr = ["Type", "Structure", "Prevalence", "Key Features"]
col_x = [Inches(0.3), Inches(2.25), Inches(3.8), Inches(4.7)]
col_w2 = [Inches(1.95), Inches(1.55), Inches(0.85), Inches(1.85)] # NOT used, just guide
for ci, hdr in enumerate(col_hdr):
add_rect(slide, col_x[ci], Inches(1.68), col_w2[ci], Inches(0.32), DARK_BLUE)
add_tb(slide, col_x[ci]+Inches(0.05), Inches(1.7), col_w2[ci]-Inches(0.1), Inches(0.3),
hdr, 11, bold=True, color=WHITE)
for ri, row in enumerate(tos_rows):
ry = Inches(2.0) + ri*Inches(0.44)
rc = WHITE if ri%2==0 else LIGHT_BLUE
for ci, cell in enumerate(row):
add_rect(slide, col_x[ci], ry, col_w2[ci], Inches(0.42), rc,
line_rgb=RGBColor(0xCC,0xCC,0xCC), line_pt=0.5)
add_tb(slide, col_x[ci]+Inches(0.05), ry+Inches(0.04), col_w2[ci]-Inches(0.1), Inches(0.35),
cell, 11, color=DARK_TEXT)
# Gruber classification
add_rect(slide, Inches(6.6), Inches(1.3), Inches(6.4), Inches(0.38), MID_BLUE)
add_tb(slide, Inches(6.65), Inches(1.32), Inches(6.3), Inches(0.35),
"Gruber's Classification (by Rib Anatomy)", 14, bold=True, color=WHITE)
gruber = [
("Type I", "Short rib — ends freely, no contact with 1st rib"),
("Type II", "Connected to 1st rib by fibrous band (most symptomatic)"),
("Type III", "Articulates with 1st rib via synchondrosis"),
("Type IV", "Complete bony rib — fused with 1st rib"),
]
for gi, (t, d) in enumerate(gruber):
ry2 = Inches(1.68) + gi*Inches(0.55)
rc2 = WHITE if gi%2==0 else LIGHT_BLUE
add_rect(slide, Inches(6.6), ry2, Inches(6.4), Inches(0.53), rc2,
line_rgb=RGBColor(0xCC,0xCC,0xCC), line_pt=0.5)
add_tb(slide, Inches(6.65), ry2+Inches(0.05), Inches(0.9), Inches(0.43),
t, 13, bold=True, color=MID_BLUE)
add_tb(slide, Inches(7.6), ry2+Inches(0.05), Inches(5.2), Inches(0.43),
d, 13, color=DARK_TEXT)
# Clinical classification note
bullet_box(slide, Inches(0.3), Inches(4.15), Inches(12.7), Inches(2.6),
["Asymptomatic — incidental finding (~90% of all cervical rib cases)",
"Neurogenic — lower trunk brachial plexus compression",
"Vascular Arterial — subclavian artery compression",
"Vascular Venous — subclavian vein compression",
"Mixed — combined neurovascular"],
font_size=14, title="By Clinical Presentation", title_size=15)
# ═══════════════════════════════════════════════════════════════════════════════
# SLIDE 7 — INVESTIGATIONS
# ═══════════════════════════════════════════════════════════════════════════════
slide = prs.slides.add_slide(blank)
bg(slide); std_header(slide, "Investigations & Provocation Tests"); footer(slide)
invest = [
("Plain X-ray (AP cervical/chest)", "Demonstrates bony cervical rib; fibrous bands invisible"),
("Duplex Ultrasonography", "Arterial/venous compression, thrombus, post-stenotic dilatation"),
("CT Angiography", "Measures costoclavicular/interscalene spaces; arterial anatomy"),
("Venography", "Gold standard for venous TOS (Harrison's 22E)"),
("NCS / EMG", "Confirms neurogenic TOS; prolonged ulnar/median conduction velocities"),
("Anterior scalene block", "Diagnostic + therapeutic — if symptoms relieved, strongly supports nTOS"),
]
add_rect(slide, Inches(0.3), Inches(1.3), Inches(5.8), Inches(0.38), MID_BLUE)
add_tb(slide, Inches(0.35), Inches(1.32), Inches(5.7), Inches(0.35),
"Investigations", 15, bold=True, color=WHITE)
for ii, (inv, find) in enumerate(invest):
ry = Inches(1.68) + ii*Inches(0.5)
rc = WHITE if ii%2==0 else LIGHT_BLUE
add_rect(slide, Inches(0.3), ry, Inches(5.8), Inches(0.48), rc,
line_rgb=RGBColor(0xCC,0xCC,0xCC), line_pt=0.5)
add_tb(slide, Inches(0.35), ry+Inches(0.02), Inches(2.1), Inches(0.44),
inv, 11, bold=True, color=DARK_BLUE)
add_tb(slide, Inches(2.5), ry+Inches(0.02), Inches(3.5), Inches(0.44),
find, 11, color=DARK_TEXT)
# Provocation tests
tests = [
("Adson's Test", "Deep breath, extend neck, rotate to affected side", "Radial pulse reduced"),
("Costoclavicular (Halsted)", "Shoulders drawn back + down (military position)", "Pulse reduced + symptoms"),
("EAST / Roos Test", "Arms at 90° ABD + ER, open-close fists x 3 min", "Symptoms reproduced ← MOST RELIABLE"),
("Wright Hyperabduction", "Arm hyperabducted overhead", "Pulse reduced"),
("ULTT (Elvey)", "Upper limb tension test (median/ulnar nerve)", "Neural paresthesias reproduced"),
]
add_rect(slide, Inches(6.4), Inches(1.3), Inches(6.6), Inches(0.38), MID_BLUE)
add_tb(slide, Inches(6.45), Inches(1.32), Inches(6.5), Inches(0.35),
"Provocation Tests (Clinical)", 15, bold=True, color=WHITE)
col_hdr2 = ["Test", "Technique", "Positive Finding"]
col_x2 = [Inches(6.4), Inches(8.2), Inches(10.4)]
col_w3 = [Inches(1.8), Inches(2.2), Inches(2.6)]
for ci, hdr in enumerate(col_hdr2):
add_rect(slide, col_x2[ci], Inches(1.68), col_w3[ci], Inches(0.32), DARK_BLUE)
add_tb(slide, col_x2[ci]+Inches(0.05), Inches(1.7), col_w3[ci]-Inches(0.1), Inches(0.28),
hdr, 11, bold=True, color=WHITE)
for ri, (t, tech, pos) in enumerate(tests):
ry = Inches(2.0) + ri*Inches(0.5)
rc = WHITE if ri%2==0 else LIGHT_BLUE
for ci, cell in enumerate([t, tech, pos]):
bld = ci==0
clr = MID_BLUE if (ri==2 and ci==2) else (DARK_BLUE if ci==0 else DARK_TEXT)
add_rect(slide, col_x2[ci], ry, col_w3[ci], Inches(0.48), rc,
line_rgb=RGBColor(0xCC,0xCC,0xCC), line_pt=0.5)
add_tb(slide, col_x2[ci]+Inches(0.05), ry+Inches(0.04), col_w3[ci]-Inches(0.1), Inches(0.4),
cell, 10, bold=bld, color=clr)
# ═══════════════════════════════════════════════════════════════════════════════
# SLIDE 8 — CONSERVATIVE TREATMENT
# ═══════════════════════════════════════════════════════════════════════════════
slide = prs.slides.add_slide(blank)
bg(slide); std_header(slide, "Treatment — Conservative Management"); footer(slide)
cons = [
"Physiotherapy — first-line treatment for neurogenic and disputed TOS (see dedicated slides)",
"NSAIDs + Muscle relaxants — for pain and spasm",
"Neuropathic agents — Gabapentin / Pregabalin for neuropathic pain component",
"Anterior scalene lidocaine block — diagnostic + therapeutic",
"Botox injection into anterior scalene — for muscle spasticity",
"Anticoagulation (LMWH / Warfarin) — for venous TOS with DVT",
"Thrombolytics — for arterial TOS with acute thrombus",
]
bullet_box(slide, Inches(0.4), Inches(1.3), Inches(12.5), Inches(3.0), cons,
font_size=14, title="Conservative Treatment Options (First-Line for nTOS)", title_size=15)
# Surgical indications
ind = [
"Failure of conservative management after 3-6 months",
"Progressive neurological symptoms — wasting, weakness",
"Prolonged ulnar / median nerve conduction velocities on NCS",
"Arterial TOS — ALWAYS requires surgery (Current Surgical Therapy 14e)",
"Venous TOS with subclavian thrombosis",
]
bullet_box(slide, Inches(0.4), Inches(4.55), Inches(12.5), Inches(2.5), ind,
font_size=14, title="Indications for Surgery", title_size=15)
# quote
add_tb(slide, Inches(0.6), Inches(7.0), Inches(12.0), Inches(0.35),
"\"Brachial plexus involvement with minimal signs and symptoms often responds to conservative treatment with physiotherapy and shoulder girdle exercises.\" — Rosen's Emergency Medicine",
10, italic=True, color=RGBColor(0x44,0x66,0x88))
# ═══════════════════════════════════════════════════════════════════════════════
# SLIDE 9 — SURGICAL MANAGEMENT
# ═══════════════════════════════════════════════════════════════════════════════
slide = prs.slides.add_slide(blank)
bg(slide); std_header(slide, "Surgical Management"); footer(slide)
# Goals
goals = [
"Excision of the cervical rib",
"Resection of the 1st rib",
"Division of fibromuscular bands",
"Anterior scalenectomy (scalene muscle release)",
"Vascular reconstruction (for arterial TOS)",
]
bullet_box(slide, Inches(0.3), Inches(1.3), Inches(4.2), Inches(2.5), goals,
font_size=13, title="Surgical Goals", title_size=14)
# Approaches table
add_rect(slide, Inches(4.7), Inches(1.3), Inches(8.3), Inches(0.38), MID_BLUE)
add_tb(slide, Inches(4.75), Inches(1.32), Inches(8.2), Inches(0.35),
"Surgical Approaches", 15, bold=True, color=WHITE)
approaches = [
("Transaxillary", "nTOS, vTOS (~99%)", "Best cosmesis; wide exposure; preferred approach"),
("Supraclavicular", "nTOS with fibrous bands", "Direct brachial plexus view; scalenectomy + neurolysis"),
("Paraclavicular", "aTOS", "Best exposure for vascular reconstruction"),
("Infraclavicular", "aTOS - vascular", "Access for arterial reconstruction"),
("Robot-assisted / VATS", "Select cases", "Minimally invasive; newer technique"),
]
a_col_x = [Inches(4.7), Inches(7.0), Inches(9.2)]
a_col_w = [Inches(2.3), Inches(2.2), Inches(3.8)]
a_hdrs = ["Approach", "Best For", "Key Advantage"]
for ci, hdr in enumerate(a_hdrs):
add_rect(slide, a_col_x[ci], Inches(1.68), a_col_w[ci], Inches(0.32), DARK_BLUE)
add_tb(slide, a_col_x[ci]+Inches(0.05), Inches(1.7), a_col_w[ci]-Inches(0.1), Inches(0.28),
hdr, 11, bold=True, color=WHITE)
for ri, row in enumerate(approaches):
ry = Inches(2.0) + ri*Inches(0.47)
rc = WHITE if ri%2==0 else LIGHT_BLUE
for ci, cell in enumerate(row):
add_rect(slide, a_col_x[ci], ry, a_col_w[ci], Inches(0.45), rc,
line_rgb=RGBColor(0xCC,0xCC,0xCC), line_pt=0.5)
add_tb(slide, a_col_x[ci]+Inches(0.05), ry+Inches(0.04), a_col_w[ci]-Inches(0.1), Inches(0.37),
cell, 11, bold=(ci==0), color=(DARK_BLUE if ci==0 else DARK_TEXT))
# Arterial TOS note
bullet_box(slide, Inches(0.3), Inches(3.95), Inches(12.7), Inches(1.4),
["Arterial TOS universally requires surgery + may need: thrombolysis, embolectomy, axillosubclavian artery reconstruction, or distal bypass (Current Surgical Therapy 14e).",
"Post-op: early passive mobilization, physiotherapy, anticoagulation where indicated.",
"Monitor: pneumothorax, vascular injury, nerve injury, Horner's syndrome."],
font_size=13, title="Arterial TOS & Post-Operative Management", title_size=14)
# Prognosis
bullet_box(slide, Inches(0.3), Inches(5.5), Inches(12.7), Inches(1.6),
["Surgery halts neurological progression but wasting/weakness often does NOT fully recover (Harrison's 22E).",
"Arterial and venous TOS have good prognosis post-decompression.",
"Sabiston: \"Surgery is beneficial in most patients with TOS, and long-term functional outcome is good.\""],
font_size=13, title="Prognosis", title_size=14)
# ═══════════════════════════════════════════════════════════════════════════════
# SLIDE 10 — PHYSIOTHERAPY OVERVIEW
# ═══════════════════════════════════════════════════════════════════════════════
slide = prs.slides.add_slide(blank)
bg(slide); std_header(slide, "Physiotherapy Management — Overview & Assessment"); footer(slide)
goals_pt = [
"Decrease pain and neurological symptoms",
"Correct posture — forward head and rounded shoulder",
"Restore cervical, thoracic, and shoulder ROM",
"Strengthen scapular stabilizers and shoulder girdle",
"Improve neuromuscular control and scapular kinematics",
"Normalize breathing patterns",
"Functional rehabilitation and return to work / sport",
]
bullet_box(slide, Inches(0.3), Inches(1.3), Inches(6.0), Inches(3.5), goals_pt,
font_size=13, title="Goals of Physiotherapy", title_size=14)
assess = [
"Forward head posture — increases lower brachial plexus tension",
"Drooping / rounded shoulders — narrows all three outlet spaces",
"Thoracic kyphosis",
"Scapular dyskinesia — winging, anterior tipping",
]
bullet_box(slide, Inches(6.5), Inches(1.3), Inches(6.5), Inches(2.1), assess,
font_size=13, title="Postural Assessment", title_size=14)
tests_pt = [
"EAST / Roos Test — most reliable",
"Adson's Test",
"ULTT 1, 3 (Elvey) — neural tension",
"Wright's Hyperabduction Test",
"Costoclavicular Manoeuvre",
"Cervical Rotation Lateral Flexion Test (CRLT) — first rib mobility",
"Tinel's Sign over brachial plexus (supraclavicular)",
]
bullet_box(slide, Inches(6.5), Inches(3.55), Inches(6.5), Inches(3.1), tests_pt,
font_size=13, title="Special Tests for Assessment", title_size=14)
ref_note = "Ref: Jayant Joshi — Essentials of Orthopaedics & Applied Physiotherapy | PMC11277273 (2024) | Brigham & Women's TOS Standard of Care"
add_tb(slide, Inches(0.3), Inches(4.95), Inches(6.0), Inches(0.4),
ref_note, 9, italic=True, color=RGBColor(0x55,0x55,0x99), wrap=True)
# ═══════════════════════════════════════════════════════════════════════════════
# SLIDE 11 — PHYSIO PHASE 1 & 2
# ═══════════════════════════════════════════════════════════════════════════════
slide = prs.slides.add_slide(blank)
bg(slide); std_header(slide, "Physiotherapy — Phase 1: Acute & Phase 2: Mobility"); footer(slide)
p1 = [
"TENS — pain modulation",
"Pulsed ultrasound over scalene region (low intensity)",
"Cryotherapy / Heat as appropriate",
"Interferential therapy (IFT) — deeper pain relief",
"Arm supported in sling — relieve neural tension",
"Avoid overhead activities and provocative postures",
"Sleep positioning advice — avoid arm overhead",
"Ergonomic workstation setup + patient education",
]
bullet_box(slide, Inches(0.3), Inches(1.3), Inches(6.2), Inches(5.4), p1,
font_size=13, title="Phase 1 — Acute / Pain Management", title_size=14)
# Phase 2 - Joint mobilizations
mob = [
("Inferior 1st rib mobilization", "Increases costoclavicular space — MOST IMPORTANT"),
("Cervicothoracic junction mob.", "Improves upper thoracic mobility"),
("Thoracic PA mobilization", "Reduces kyphosis, improves posture"),
("Lateral cervical glides", "Reduces scalene hypertonicity"),
("Glenohumeral mobilization", "If ROM deficits exist"),
]
add_rect(slide, Inches(6.7), Inches(1.3), Inches(6.3), Inches(0.38), MID_BLUE)
add_tb(slide, Inches(6.75), Inches(1.32), Inches(6.2), Inches(0.35),
"Phase 2 — Joint Mobilization (Maitland Grade I-III)", 13, bold=True, color=WHITE)
for mi, (mn, mr) in enumerate(mob):
ry = Inches(1.68) + mi*Inches(0.48)
rc = WHITE if mi%2==0 else LIGHT_BLUE
add_rect(slide, Inches(6.7), ry, Inches(6.3), Inches(0.46), rc,
line_rgb=RGBColor(0xCC,0xCC,0xCC), line_pt=0.5)
add_tb(slide, Inches(6.75), ry+Inches(0.04), Inches(2.8), Inches(0.38),
mn, 12, bold=True, color=DARK_BLUE)
add_tb(slide, Inches(9.6), ry+Inches(0.04), Inches(3.3), Inches(0.38),
mr, 12, color=DARK_TEXT)
soft = [
"Anterior scalene stretch — ipsilateral rotation + contralateral side bend",
"Middle scalene stretch — cervical lateral flexion away from affected side",
"Pectoralis minor stretch — doorway / corner stretch",
"Upper trapezius + levator scapulae release",
"Suboccipital release",
"Contract-relax (PNF) for scalene inhibition",
]
bullet_box(slide, Inches(6.7), Inches(4.12), Inches(6.3), Inches(2.6), soft,
font_size=12, title="Soft Tissue & Neural Mobilization", title_size=13)
# ═══════════════════════════════════════════════════════════════════════════════
# SLIDE 12 — PHYSIO PHASE 3 & 4
# ═══════════════════════════════════════════════════════════════════════════════
slide = prs.slides.add_slide(blank)
bg(slide); std_header(slide, "Physiotherapy — Phase 3: Strengthening & Phase 4: Functional"); footer(slide)
# Strengthening table
muscles = [
("Lower Trapezius", "Prone Y-exercises, prone shoulder extension"),
("Middle Trapezius", "Prone T-exercises, seated low row"),
("Serratus Anterior", "Wall push-up plus, serratus punches, shoulder protraction"),
("Rhomboids", "Scapular retraction exercises"),
("Deep Cervical Flexors", "Chin tuck exercises (progressive against gravity)"),
("Rotator Cuff", "Strengthening in neutral range — avoid impingement positions"),
]
add_rect(slide, Inches(0.3), Inches(1.3), Inches(6.3), Inches(0.38), MID_BLUE)
add_tb(slide, Inches(0.35), Inches(1.32), Inches(6.2), Inches(0.35),
"Phase 3 — Scapular Stabilizer Strengthening (SOR: A)", 13, bold=True, color=WHITE)
m_cols = [Inches(0.3), Inches(2.7)]
m_ws = [Inches(2.4), Inches(3.9)]
m_hdrs = ["Muscle Group", "Exercises"]
for ci, hdr in enumerate(m_hdrs):
add_rect(slide, m_cols[ci], Inches(1.68), m_ws[ci], Inches(0.32), DARK_BLUE)
add_tb(slide, m_cols[ci]+Inches(0.05), Inches(1.7), m_ws[ci]-Inches(0.1), Inches(0.28),
hdr, 11, bold=True, color=WHITE)
for ri, (mg, ex) in enumerate(muscles):
ry = Inches(2.0) + ri*Inches(0.5)
rc = WHITE if ri%2==0 else LIGHT_BLUE
for ci, cell in enumerate([mg, ex]):
add_rect(slide, m_cols[ci], ry, m_ws[ci], Inches(0.48), rc,
line_rgb=RGBColor(0xCC,0xCC,0xCC), line_pt=0.5)
add_tb(slide, m_cols[ci]+Inches(0.05), ry+Inches(0.04), m_ws[ci]-Inches(0.1), Inches(0.40),
cell, 12, bold=(ci==0), color=(DARK_BLUE if ci==0 else DARK_TEXT))
posture = [
"Chin tuck — corrects forward head posture",
"Wall angels — scapular control + thoracic extension",
"Brugger's postural relief position",
"Scapular retraction + depression exercises",
"Diaphragmatic breathing — reduces scalene overuse as accessory breathing muscles (chronically elevated 1st rib)",
]
bullet_box(slide, Inches(0.3), Inches(5.15), Inches(6.3), Inches(1.9), posture,
font_size=12, title="Postural Re-education + Breathing", title_size=13)
# Phase 4
p4 = [
"Functional task training — occupation / sport-specific progressive loading",
"Gradual return to overhead activities with maintained scapular control",
"Ergonomic workstation assessment and setup",
"Home exercise program — continued indefinitely post-discharge (Brigham & Women's)",
"Return to sport criteria: full ROM + symmetrical strength + negative EAST test",
]
bullet_box(slide, Inches(6.8), Inches(1.3), Inches(6.2), Inches(2.7), p4,
font_size=13, title="Phase 4 — Functional Rehabilitation", title_size=14)
# Neural mobilization note
nm = [
"Median nerve slider — ULTT1 position",
"Ulnar nerve slider — ULTT2/ULTT3",
"Brachial plexus neural flossing — sliders before tensioners",
"CAUTION: TOS is often traction-based — grade neural mobilization carefully",
"Never force through reproduction of paresthesias",
]
bullet_box(slide, Inches(6.8), Inches(4.15), Inches(6.2), Inches(2.5), nm,
font_size=12, title="Neural Mobilization (Nerve Gliding)", title_size=13)
# ═══════════════════════════════════════════════════════════════════════════════
# SLIDE 13 — POST-SURGICAL PHYSIO
# ═══════════════════════════════════════════════════════════════════════════════
slide = prs.slides.add_slide(blank)
bg(slide); std_header(slide, "Post-Surgical Physiotherapy Protocol"); footer(slide)
phases = [
("Phase 1\n0–2 Weeks\n(Immediate Post-Op)", DARK_BLUE, [
"Codman's pendulum exercises",
"Passive ROM of shoulder",
"TENS + cryotherapy",
"Edema management",
"Sling use as required",
]),
("Phase 2\n2–6 Weeks\n(Early Rehab)", MID_BLUE, [
"Active-assisted + active ROM",
"First rib mobilization (if access permits)",
"Scapular setting exercises",
"Neural gliding — gentle sliders",
"Postural correction exercises",
]),
("Phase 3\n6–12 Weeks\n(Late Rehab)", RGBColor(0x00, 0x7A, 0x5E), [
"Progressive scapular strengthening",
"Functional activities + ADL",
"Return to work/sport",
"Full physiotherapy protocol",
"Progressive loading",
]),
("Phase 4\n12+ Weeks\n(Discharge)", RGBColor(0x6A, 0x35, 0x9C), [
"Independent home exercise program",
"Ergonomic advice",
"Self-management strategies",
"Annual follow-up review",
"Indefinite HEP continuation",
]),
]
px = [Inches(0.3), Inches(3.55), Inches(6.8), Inches(10.05)]
for pi, (title, color, items) in enumerate(phases):
x = px[pi]; w = Inches(3.0)
# header card
hdr_card = slide.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE, x, Inches(1.3), w, Inches(1.1))
hdr_card.fill.solid(); hdr_card.fill.fore_color.rgb = color
hdr_card.line.fill.background(); hdr_card.shadow.inherit = False
add_tb(slide, x+Inches(0.1), Inches(1.33), w-Inches(0.2), Inches(1.04),
title, 13, bold=True, color=WHITE, align=PP_ALIGN.CENTER)
# items card
ic = slide.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE, x, Inches(2.5), w, Inches(3.5))
ic.fill.solid(); ic.fill.fore_color.rgb = WHITE
ic.line.color.rgb = color; ic.line.width = Pt(1.5)
ic.shadow.inherit = False
tbx = slide.shapes.add_textbox(x+Inches(0.15), Inches(2.6), w-Inches(0.3), Inches(3.2))
tf3 = tbx.text_frame; tf3.word_wrap = True
tf3.margin_left = tf3.margin_right = tf3.margin_top = tf3.margin_bottom = 0
for idx, item in enumerate(items):
if idx == 0:
tf3.paragraphs[0].alignment = PP_ALIGN.LEFT
r4 = tf3.paragraphs[0].add_run()
r4.text = " " + item; r4.font.name = "Calibri"; r4.font.size = Pt(13)
r4.font.color.rgb = DARK_TEXT
else:
p3 = tf3.add_paragraph(); p3.alignment = PP_ALIGN.LEFT
r5 = p3.add_run(); r5.text = " " + item
r5.font.name = "Calibri"; r5.font.size = Pt(13)
r5.font.color.rgb = DARK_TEXT
# special note
bullet_box(slide, Inches(0.3), Inches(6.2), Inches(12.7), Inches(0.85),
["Blood Flow Restriction (BFR) training — NOT recommended in TOS (SOR: C) — risk of upper extremity DVT in arterial/venous TOS (PMC11277273, 2024)."],
font_size=13, title="Special Consideration", title_size=13)
# ═══════════════════════════════════════════════════════════════════════════════
# SLIDE 14 — REFERENCES
# ═══════════════════════════════════════════════════════════════════════════════
slide = prs.slides.add_slide(blank)
bg(slide); std_header(slide, "References"); footer(slide)
refs = [
"Gray's Anatomy for Students (9780323934237) — Cervical rib anatomy, fibrous bands, clinical presentations",
"Bailey & Love's Short Practice of Surgery, 28th Ed. (9780367548117) — Cervical rib definition, TOS management",
"Harrison's Principles of Internal Medicine, 22E (9781265977061) — TOS classification, treatment recommendations",
"S. Das — A Manual on Clinical Surgery, 13th Ed. (9788190568104) — Symptom groups: local, neurogenic, vascular",
"Sabiston Textbook of Surgery (9780443124341) — Clinical presentation table, physical examination tests",
"Current Surgical Therapy, 14e (9780323796835) — Surgical approaches, EAST test, arterial TOS management",
"Fischer's Mastery of Surgery, 8th Ed. — Supraclavicular approach, combined cervical + first rib resection",
"THIEME Atlas of General Anatomy & Musculoskeletal System (9781626237186) — Scalene syndrome anatomy",
"Rheumatology 2-Volume Set, Elsevier 2022 — TOS diagnosis, Adson maneuver, provocation tests",
"Jayant Joshi — Essentials of Orthopaedics & Applied Physiotherapy, Elsevier India — Clinical tests, physiotherapy framework",
"Panther EJ et al. J Shoulder Elbow Surg 2022 [PMID: 35963513] — TOS review",
"Cavanna AC et al. J Osteopath Med 2022 [PMID: 36018621] — Systematic review of TOS",
"Mulatti GC et al. Semin Vasc Surg 2024 [PMID: 38704183] — TOS in women",
"Potluri VK et al. Semin Vasc Surg 2024 [PMID: 38704178] — Arterial TOS review",
"PMC11277273 (2024) — Current Clinical Concepts: Rehabilitation of Thoracic Outlet Syndrome",
"Brigham & Women's Hospital — Standard of Care: Thoracic Outlet Syndrome (non-operative)",
]
tbx = slide.shapes.add_textbox(Inches(0.5), Inches(1.3), Inches(12.5), Inches(5.8))
tf_r = tbx.text_frame; tf_r.word_wrap = True
tf_r.margin_left = tf_r.margin_right = tf_r.margin_top = tf_r.margin_bottom = 0
first_r = True
for ref in refs:
if first_r:
tf_r.paragraphs[0].alignment = PP_ALIGN.LEFT
r6 = tf_r.paragraphs[0].add_run()
r6.text = ref; r6.font.name = "Calibri"; r6.font.size = Pt(12)
r6.font.color.rgb = DARK_TEXT; first_r = False
else:
p_r = tf_r.add_paragraph(); p_r.alignment = PP_ALIGN.LEFT
r7 = p_r.add_run(); r7.text = ref
r7.font.name = "Calibri"; r7.font.size = Pt(12)
r7.font.color.rgb = DARK_TEXT
# blank line
p_blank = tf_r.add_paragraph()
p_blank.add_run().text = ""
# ─── SAVE ────────────────────────────────────────────────────────────────────
out_path = "/tmp/workspace/cervical-rib-ppt/Cervical_Rib_TOS.pptx"
from io import BytesIO
buf = BytesIO()
prs.save(buf)
with open(out_path, "wb") as f:
f.write(buf.getvalue())
print(f"Saved: {out_path}")