prepare a chart with all details in one page on the topic of lacrimal sac syringing with diagram and made it attrective in jpg formate
mkdir -p /home/daytona/workspace/lacrimal-chart && ls /home/daytona/workspace/
cd /home/daytona/workspace/lacrimal-chart && curl -s -o irrigation_photo.png "https://cdn.orris.care/cdss_images/de4ec1847accf892cb2bdb188c044dce63c503be1863f60218f095d2a5a75c47.png" && curl -s -o punctum_dilator.png "https://cdn.orris.care/cdss_images/7680d3076bdb7d1a80ee819b473d7a15de712ab71fd836707b453e6e778bc0ab.png" && echo "Downloaded"
~/lacrimal-chart/create_chart.py
from PIL import Image, ImageDraw, ImageFont
import textwrap
import math
# Canvas: A3 landscape at 150 DPI
W, H = 4961, 3508 # A3 at 300 DPI for print quality, but let's use A3 at 150dpi => 2480x1754, scale up
W, H = 4200, 2970 # A3 landscape 300 DPI effective
# Color palette - professional teal/navy medical theme
BG = (10, 24, 48) # deep navy background
HEADER_BG = (0, 120, 130) # teal header
CARD_BG = (18, 38, 68) # slightly lighter navy cards
CARD_BG2 = (15, 55, 75) # teal-tinted card
ACCENT = (0, 200, 210) # bright teal accent
ACCENT2 = (255, 165, 50) # warm orange accent
WHITE = (255, 255, 255)
LIGHT = (180, 220, 230) # light teal text
YELLOW = (255, 220, 80)
GREEN = (80, 220, 120)
RED = (255, 100, 100)
ORANGE = (255, 165, 50)
img = Image.new("RGB", (W, H), BG)
draw = ImageDraw.Draw(img)
def load_font(size, bold=False):
try:
if bold:
return ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", size)
else:
return ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", size)
except:
return ImageFont.load_default()
def draw_rounded_rect(draw, xy, radius, fill, outline=None, outline_width=2):
x1, y1, x2, y2 = xy
draw.rectangle([x1+radius, y1, x2-radius, y2], fill=fill)
draw.rectangle([x1, y1+radius, x2, y2-radius], fill=fill)
draw.ellipse([x1, y1, x1+2*radius, y1+2*radius], fill=fill)
draw.ellipse([x2-2*radius, y1, x2, y1+2*radius], fill=fill)
draw.ellipse([x1, y2-2*radius, x1+2*radius, y2], fill=fill)
draw.ellipse([x2-2*radius, y2-2*radius, x2, y2], fill=fill)
if outline:
draw.arc([x1, y1, x1+2*radius, y1+2*radius], 180, 270, fill=outline, width=outline_width)
draw.arc([x2-2*radius, y1, x2, y1+2*radius], 270, 360, fill=outline, width=outline_width)
draw.arc([x1, y2-2*radius, x1+2*radius, y2], 90, 180, fill=outline, width=outline_width)
draw.arc([x2-2*radius, y2-2*radius, x2, y2], 0, 90, fill=outline, width=outline_width)
draw.line([x1+radius, y1, x2-radius, y1], fill=outline, width=outline_width)
draw.line([x1+radius, y2, x2-radius, y2], fill=outline, width=outline_width)
draw.line([x1, y1+radius, x1, y2-radius], fill=outline, width=outline_width)
draw.line([x2, y1+radius, x2, y2-radius], fill=outline, width=outline_width)
def draw_text_wrapped(draw, text, x, y, font, color, max_width, line_spacing=8):
"""Draw text wrapped to max_width pixels."""
words = text.split()
lines = []
current = ""
for w in words:
test = (current + " " + w).strip()
bbox = font.getbbox(test)
if bbox[2] - bbox[0] <= max_width:
current = test
else:
if current:
lines.append(current)
current = w
if current:
lines.append(current)
cy = y
line_h = font.getbbox("Ag")[3] - font.getbbox("Ag")[1]
for line in lines:
draw.text((x, cy), line, font=font, fill=color)
cy += line_h + line_spacing
return cy
# ─── HEADER ───────────────────────────────────────────────────────────────────
# Gradient-like header: draw multiple horizontal rectangles
for i in range(160):
t = i / 160
r = int(0 + t * 0)
g = int(120 + t * (80-120))
b = int(130 + t * (100-130))
draw.rectangle([(0, i), (W, i+1)], fill=(r, g, b))
# Decorative left bar
draw.rectangle([(0, 0), (14, 160)], fill=ACCENT2)
# Title
fnt_title = load_font(90, bold=True)
fnt_subtitle = load_font(42)
fnt_h1 = load_font(44, bold=True)
fnt_h2 = load_font(36, bold=True)
fnt_body = load_font(28)
fnt_small = load_font(24)
fnt_tiny = load_font(20)
fnt_tag = load_font(26, bold=True)
draw.text((40, 18), "LACRIMAL SAC SYRINGING", font=fnt_title, fill=WHITE)
draw.text((44, 115), "Lacrimal Irrigation · Diagnostic & Therapeutic Procedure | Kanski's Clinical Ophthalmology", font=fnt_subtitle, fill=(200, 235, 240))
# Eye icon decoration (simple)
draw.ellipse([(W-200, 20), (W-30, 140)], outline=ACCENT, width=4)
draw.ellipse([(W-155, 50), (W-75, 115)], fill=ACCENT)
draw.ellipse([(W-140, 65), (W-90, 105)], fill=(10,24,48))
draw.ellipse([(W-130, 72), (W-108, 95)], fill=(120,200,255))
# ─── TOP STRIP - KEY FACTS ───────────────────────────────────────────────────
strip_y = 165
draw.rectangle([(0, strip_y), (W, strip_y+68)], fill=(0, 80, 100))
facts = [
"🔹 Also called: Lacrimal Irrigation",
"🔹 Instrument: 26/27G blunt-tipped cannula",
"🔹 Fluid: Normal saline, 3 ml syringe",
"🔹 Route: Lower punctum (preferred)",
"🔹 Anaesthesia: Topical LA drops",
]
fx = 30
for f in facts:
draw.text((fx, strip_y+14), f, font=fnt_small, fill=YELLOW)
fw = fnt_small.getbbox(f)[2]
fx += fw + 60
# ─── MAIN CONTENT AREA ───────────────────────────────────────────────────────
# Three columns below the strip
COL_Y = strip_y + 78
COL_H = H - COL_Y - 50
PAD = 22
M = 20 # margin between cols
col_w = (W - 4*M) // 3
c1x = M
c2x = c1x + col_w + M
c3x = c2x + col_w + M
# ═══ COLUMN 1 ════════════════════════════════════════════════════════════════
# Box 1A - Anatomy Recap
bx, by, bw = c1x, COL_Y, col_w
draw_rounded_rect(draw, (bx, by, bx+bw, by+195), 14, CARD_BG2, outline=ACCENT, outline_width=3)
draw.rectangle([(bx, by), (bx+bw, by+50)], fill=ACCENT)
draw.text((bx+PAD, by+10), "ANATOMY OF LACRIMAL DRAINAGE", font=fnt_h2, fill=WHITE)
anatomy_lines = [
("Puncta", "Posterior lid margin; face slightly posteriorly"),
("Ampullae", "Vertical 2 mm portion of each canaliculus"),
("Canaliculi", "Superior + inferior; each ~8 mm horizontal"),
("Common canaliculus", "Formed in >90% cases; opens into sac"),
("Rosenmüller valve", "Prevents reflux into canaliculi"),
("Lacrimal sac", "10–12 mm; lies in lacrimal fossa"),
("NLD", "Drains into inferior meatus of nose"),
]
fy = by + 58
for term, desc in anatomy_lines:
draw.text((bx+PAD, fy), f"• {term}:", font=load_font(24, bold=True), fill=YELLOW)
tw = load_font(24, bold=True).getbbox(f"• {term}:")[2]
draw.text((bx+PAD+tw+6, fy), desc, font=load_font(24), fill=LIGHT)
fy += 20
# Box 1B - Indications
b1b_y = by + 205
draw_rounded_rect(draw, (bx, b1b_y, bx+bw, b1b_y+210), 14, CARD_BG, outline=ACCENT2, outline_width=3)
draw.rectangle([(bx, b1b_y), (bx+bw, b1b_y+48)], fill=ACCENT2)
draw.text((bx+PAD, b1b_y+8), "INDICATIONS", font=fnt_h1, fill=(20,20,20))
inds = [
"✔ Epiphora (watering eye) – diagnostic",
"✔ Suspected nasolacrimal duct obstruction",
"✔ Pre-operative assessment for DCR",
"✔ Identifying level of obstruction",
"✔ Mucocele / chronic dacryocystitis",
"✔ Post-operative patency check",
"✔ Therapeutic irrigation of infected sac",
]
iy = b1b_y + 56
for ind in inds:
draw.text((bx+PAD, iy), ind, font=fnt_body, fill=WHITE)
iy += 22
# Box 1C - Contraindications
b1c_y = b1b_y + 220
draw_rounded_rect(draw, (bx, b1c_y, bx+bw, b1c_y+160), 14, (45,10,10), outline=RED, outline_width=3)
draw.rectangle([(bx, b1c_y), (bx+bw, b1c_y+46)], fill=RED)
draw.text((bx+PAD, b1c_y+8), "CONTRAINDICATIONS", font=fnt_h1, fill=WHITE)
cons = [
"✘ Acute dacryocystitis (relative – avoid irrigation)",
"✘ Absent / severely stenosed punctum",
"✘ Known canalicular laceration",
"✘ Suspected lacrimal tumour (irrigate gently)",
]
cy2 = b1c_y + 54
for c in cons:
draw.text((bx+PAD, cy2), c, font=fnt_body, fill=(255,180,180))
cy2 += 26
# ═══ COLUMN 2 ════════════════════════════════════════════════════════════════
# Box 2A - Step-by-step Procedure
b2x = c2x
draw_rounded_rect(draw, (b2x, COL_Y, b2x+col_w, COL_Y+540), 14, CARD_BG, outline=ACCENT, outline_width=3)
draw.rectangle([(b2x, COL_Y), (b2x+col_w, COL_Y+48)], fill=ACCENT)
draw.text((b2x+PAD, COL_Y+7), "STEP-BY-STEP PROCEDURE", font=fnt_h1, fill=WHITE)
steps = [
("1", "Patient Preparation", "Explain procedure. Patient seated or supine. Tissue available to catch saline."),
("2", "Topical Anaesthesia", "Instil LA drops (e.g. proxymetacaine 0.5%) into conjunctival sac. Wait 1–2 min."),
("3", "Punctum Dilation", "Use Nettleship punctum dilator — enter vertically 1–2 mm then tilt horizontal. Apply lateral lid tension."),
("4", "Cannula Insertion", "26/27G blunt cannula on 3 ml saline syringe. Enter lower punctum vertically 2 mm then rotate horizontal. Maintain lateral lid tension."),
("5", "Advance Cannula", "Advance horizontally ~8 mm along canaliculus toward medial canthus. Note hard vs soft stop."),
("6", "Irrigate Gently", "Gently depress plunger. Ask patient if they can taste/feel saline in throat (positive = patent)."),
("7", "Record Findings", "Document stop type, flow, reflux character, and whether nose/throat reached."),
]
sy = COL_Y + 56
step_colors = [ACCENT, GREEN, YELLOW, ORANGE, ACCENT, GREEN, YELLOW]
for i, (num, title, desc) in enumerate(steps):
# Circle number
cx_s = b2x + PAD
draw.ellipse([(cx_s, sy), (cx_s+34, sy+34)], fill=step_colors[i])
draw.text((cx_s+9, sy+4), num, font=load_font(22, bold=True), fill=(10,20,40))
draw.text((b2x+PAD+42, sy+2), title, font=load_font(26, bold=True), fill=step_colors[i])
# Description
draw_text_wrapped(draw, desc, b2x+PAD+42, sy+26, load_font(21), LIGHT, col_w-PAD*2-42, line_spacing=4)
sy += 70
# Box 2B - Hard Stop vs Soft Stop
b2b_y = COL_Y + 550
draw_rounded_rect(draw, (b2x, b2b_y, b2x+col_w, b2b_y+270), 14, CARD_BG2, outline=ACCENT2, outline_width=3)
draw.rectangle([(b2x, b2b_y), (b2x+col_w, b2b_y+46)], fill=ACCENT2)
draw.text((b2x+PAD, b2b_y+7), "HARD STOP vs SOFT STOP", font=fnt_h1, fill=(20,20,20))
# Hard stop box
hs_x = b2x + PAD
hs_y = b2b_y + 55
draw_rounded_rect(draw, (hs_x, hs_y, hs_x+(col_w-3*PAD)//2, hs_y+190), 10, (10,40,20), outline=GREEN, outline_width=3)
draw.text((hs_x+10, hs_y+8), "HARD STOP ✔", font=load_font(26, bold=True), fill=GREEN)
hard_lines = [
"Cannula reaches medial wall",
"Stops against lacrimal bone",
"Canaliculi are PATENT",
"Obstruction is distal",
"(NLD / sac level)",
"",
"→ Saline → nose/throat",
"→ Reflux via opposite",
" punctum if NLD blocked",
]
hly = hs_y + 36
for hl in hard_lines:
draw.text((hs_x+10, hly), hl, font=load_font(21), fill=(180,255,180))
hly += 20
# Soft stop box
ss_x = hs_x + (col_w-3*PAD)//2 + PAD
draw_rounded_rect(draw, (ss_x, hs_y, b2x+col_w-PAD, hs_y+190), 10, (40,10,10), outline=RED, outline_width=3)
draw.text((ss_x+10, hs_y+8), "SOFT STOP ✘", font=load_font(26, bold=True), fill=RED)
soft_lines = [
"Cannula stopped by",
"soft tissue obstruction",
"Canalicular OBSTRUCTION",
"(lower canaliculus)",
"",
"→ Reflux via same",
" (lower) punctum",
"→ Consider probing",
]
sly = hs_y + 36
for sl in soft_lines:
draw.text((ss_x+10, sly), sl, font=load_font(21), fill=(255,180,180))
sly += 20
# ═══ COLUMN 3 ════════════════════════════════════════════════════════════════
b3x = c3x
# Box 3A - Textbook Photo + Diagram
draw_rounded_rect(draw, (b3x, COL_Y, b3x+col_w, COL_Y+360), 14, CARD_BG, outline=ACCENT, outline_width=3)
draw.rectangle([(b3x, COL_Y), (b3x+col_w, COL_Y+46)], fill=ACCENT)
draw.text((b3x+PAD, COL_Y+7), "PROCEDURE ILLUSTRATIONS", font=fnt_h1, fill=WHITE)
# Load and embed the textbook images
try:
photo = Image.open("/home/daytona/workspace/lacrimal-chart/irrigation_photo.png").convert("RGB")
diag = Image.open("/home/daytona/workspace/lacrimal-chart/punctum_dilator.png").convert("RGB")
# Resize to fit side by side
img_h = 270
img_w = (col_w - 3*PAD) // 2
photo_ar = photo.width / photo.height
diag_ar = diag.width / diag.height
photo = photo.resize((int(img_h*photo_ar), img_h), Image.LANCZOS)
diag = diag.resize((int(img_h*diag_ar), img_h), Image.LANCZOS)
# Crop/pad to equal width
def fit_width(im, target_w):
if im.width > target_w:
left = (im.width - target_w)//2
im = im.crop((left, 0, left+target_w, im.height))
return im
photo = fit_width(photo, img_w)
diag = fit_width(diag, img_w)
img.paste(photo, (b3x+PAD, COL_Y+54))
img.paste(diag, (b3x+PAD+img_w+PAD, COL_Y+54))
# Captions
draw.text((b3x+PAD, COL_Y+54+img_h+4), "Clinical photo: Irrigation", font=fnt_tiny, fill=LIGHT)
draw.text((b3x+PAD+img_w+PAD, COL_Y+54+img_h+4), "Diagram: Punctum dilation", font=fnt_tiny, fill=LIGHT)
except Exception as e:
draw.text((b3x+PAD, COL_Y+54), f"[Image error: {e}]", font=fnt_body, fill=RED)
# Box 3B - Interpretation of Results
b3b_y = COL_Y + 370
draw_rounded_rect(draw, (b3x, b3b_y, b3x+col_w, b3b_y+310), 14, CARD_BG2, outline=ACCENT2, outline_width=3)
draw.rectangle([(b3x, b3b_y), (b3x+col_w, b3b_y+46)], fill=(60, 20, 100))
draw.text((b3x+PAD, b3b_y+7), "INTERPRETATION OF RESULTS", font=fnt_h1, fill=WHITE)
results = [
(GREEN, "Patent system", "Saline tasted in throat. No reflux."),
(ACCENT, "Functional obstruction", "Saline reaches throat but epiphora persists → pump failure."),
(YELLOW, "NLD obstruction", "Hard stop achieved but saline refluxes via upper & lower puncta. Sac may distend. Reflux may be clear, mucoid, or mucopurulent."),
(ORANGE, "Lower canalicular block","Soft stop. Reflux via lower punctum."),
(RED, "Common canaliculus block","Hard stop. Reflux via upper punctum only."),
(RED, "Both canaliculi blocked", "Soft stop on both sides. Separate bilateral obstruction."),
]
ry = b3b_y + 54
for color, label, desc in results:
# Colored indicator bar
draw.rectangle([(b3x+PAD, ry), (b3x+PAD+8, ry+46)], fill=color)
draw.text((b3x+PAD+16, ry), label, font=load_font(25, bold=True), fill=color)
draw_text_wrapped(draw, desc, b3x+PAD+16, ry+22, load_font(21), LIGHT, col_w-PAD*2-16, line_spacing=4)
ry += 52
# Box 3C - Equipment + Complications
b3c_y = b3b_y + 320
draw_rounded_rect(draw, (b3x, b3c_y, b3x+col_w, H-50), 14, CARD_BG, outline=ACCENT, outline_width=3)
draw.rectangle([(b3x, b3c_y), (b3x+col_w, b3c_y+46)], fill=(20, 80, 80))
draw.text((b3x+PAD, b3c_y+7), "EQUIPMENT & COMPLICATIONS", font=fnt_h1, fill=WHITE)
# Equipment
draw.text((b3x+PAD, b3c_y+54), "Equipment:", font=load_font(26, bold=True), fill=YELLOW)
equip = ["• Topical LA (proxymetacaine 0.5%)", "• Nettleship punctum dilator",
"• 26/27G blunt lacrimal cannula", "• 3 ml syringe + normal saline",
"• Tissues / kidney dish"]
ey = b3c_y + 78
for eq in equip:
draw.text((b3x+PAD, ey), eq, font=fnt_body, fill=LIGHT)
ey += 24
# Complications
draw.text((b3x+PAD, ey+8), "Complications:", font=load_font(26, bold=True), fill=ORANGE)
comps = ["• False passage (over-aggressive probing)",
"• Canalicular trauma / scarring",
"• Infection spread (avoid in acute DCY)",
"• Subcutaneous emphysema (rare)"]
cy3 = ey + 32
for cp in comps:
draw.text((b3x+PAD, cy3), cp, font=fnt_body, fill=(255,200,160))
cy3 += 24
# ─── ANATOMY DIAGRAM (central drawing) ──────────────────────────────────────
# Simple anatomical schematic embedded between col 1 and col 2 — no, instead draw in col 1 bottom
diag_y = b1c_y + 168
diag_x = c1x
diag_w = col_w
if diag_y + 190 < H - 50:
draw_rounded_rect(draw, (diag_x, diag_y, diag_x+diag_w, H-50), 14, (8,30,55), outline=(80,160,200), outline_width=3)
draw.text((diag_x+PAD, diag_y+8), "ANATOMY SCHEMATIC", font=load_font(28, bold=True), fill=ACCENT)
# Draw simplified anatomy
mx = diag_x + PAD + 20
my = diag_y + 45
# Upper eyelid margin line
draw.line([(mx, my+30), (mx+180, my+30)], fill=LIGHT, width=3)
draw.text((mx+185, my+22), "Upper lid margin", font=fnt_tiny, fill=LIGHT)
# Lower eyelid margin line
draw.line([(mx, my+60), (mx+180, my+60)], fill=LIGHT, width=3)
draw.text((mx+185, my+52), "Lower lid margin", font=fnt_tiny, fill=LIGHT)
# Upper punctum
draw.ellipse([(mx+160, my+24), (mx+172, my+36)], fill=RED, outline=YELLOW, width=2)
draw.text((mx+130, my+10), "Upper", font=fnt_tiny, fill=YELLOW)
draw.text((mx+130, my+21), "punctum", font=fnt_tiny, fill=YELLOW)
# Lower punctum
draw.ellipse([(mx+162, my+54), (mx+174, my+66)], fill=RED, outline=YELLOW, width=2)
draw.text((mx+132, my+68), "Lower punctum", font=fnt_tiny, fill=YELLOW)
# Upper canaliculus
draw.line([(mx+166, my+30), (mx+166, my+10)], fill=ACCENT, width=2) # vertical portion
draw.line([(mx+166, my+10), (mx+60, my+10)], fill=ACCENT, width=2) # horizontal
draw.text((mx+64, my-4), "Sup. canaliculus", font=fnt_tiny, fill=ACCENT)
# Lower canaliculus
draw.line([(mx+168, my+60), (mx+168, my+80)], fill=ACCENT, width=2)
draw.line([(mx+168, my+80), (mx+60, my+80)], fill=ACCENT, width=2)
draw.text((mx+64, my+83), "Inf. canaliculus", font=fnt_tiny, fill=ACCENT)
# Common canaliculus
draw.line([(mx+60, my+10), (mx+40, my+45)], fill=GREEN, width=2)
draw.line([(mx+60, my+80), (mx+40, my+45)], fill=GREEN, width=2)
draw.text((mx, my+38), "Common\ncanaliculus", font=fnt_tiny, fill=GREEN)
# Lacrimal sac
draw.ellipse([(mx+5, my+20), (mx+40, my+70)], outline=ORANGE, width=3)
draw.text((mx-15, my+75), "Lacrimal\nsac", font=fnt_tiny, fill=ORANGE)
# NLD arrow down
draw.line([(mx+22, my+70), (mx+22, my+110)], fill=ACCENT2, width=3)
draw.polygon([(mx+16, my+105), (mx+28, my+105), (mx+22, my+120)], fill=ACCENT2)
draw.text((mx+30, my+100), "NLD → inferior meatus", font=fnt_tiny, fill=ACCENT2)
# Rosenmüller valve mark
draw.arc([(mx+36, my+35), (mx+46, my+55)], 0, 180, fill=YELLOW, width=2)
draw.text((mx+48, my+40), "Rosenmüller\nvalve", font=fnt_tiny, fill=YELLOW)
# Cannula illustration (on the right side)
can_x = diag_x + PAD + 240
can_y = diag_y + 50
draw.text((can_x, can_y), "Syringing technique:", font=load_font(24, bold=True), fill=ACCENT)
# Draw syringe barrel
draw.rectangle([(can_x+60, can_y+30), (can_x+170, can_y+70)], fill=(180,180,200), outline=WHITE, width=2)
draw.rectangle([(can_x+50, can_y+38), (can_x+62, can_y+62)], fill=(150,150,170)) # plunger
# cannula tip
draw.line([(can_x+170, can_y+50), (can_x+250, can_y+50)], fill=YELLOW, width=3)
draw.ellipse([(can_x+246, can_y+46), (can_x+254, can_y+54)], fill=YELLOW)
draw.text((can_x+80, can_y+24), "3 ml syringe", font=fnt_tiny, fill=LIGHT)
draw.text((can_x+178, can_y+38), "26/27G", font=fnt_tiny, fill=YELLOW)
draw.text((can_x+170, can_y+52), "cannula", font=fnt_tiny, fill=YELLOW)
# Saline flow arrow
draw.line([(can_x+120, can_y+80), (can_x+250, can_y+80)], fill=(100,200,255), width=2)
draw.polygon([(can_x+245, can_y+76), (can_x+255, can_y+80), (can_x+245, can_y+84)], fill=(100,200,255))
draw.text((can_x+100, can_y+85), "Normal saline flush", font=fnt_tiny, fill=(100,200,255))
# Lateral traction note
draw.text((can_x, can_y+110), "Key technique:", font=load_font(22, bold=True), fill=ACCENT2)
draw.text((can_x, can_y+130), "• Apply lateral tension to lid", font=fnt_tiny, fill=LIGHT)
draw.text((can_x, can_y+148), "• Enter vertically 2 mm, then rotate 90°", font=fnt_tiny, fill=LIGHT)
draw.text((can_x, can_y+166), "• Advance horizontally ~8 mm", font=fnt_tiny, fill=LIGHT)
# ─── FOOTER ──────────────────────────────────────────────────────────────────
draw.rectangle([(0, H-48), (W, H)], fill=(0, 60, 70))
draw.text((PAD, H-36), "Source: Kanski's Clinical Ophthalmology, 10th Ed.", font=fnt_small, fill=(160,210,220))
draw.text((W//2-200, H-36), "For educational use only — Not a substitute for clinical training", font=fnt_small, fill=(140,180,190))
draw.text((W-500, H-36), "Prepared: June 2026", font=fnt_small, fill=(140,180,190))
# ─── SAVE ────────────────────────────────────────────────────────────────────
out_path = "/home/daytona/workspace/lacrimal-chart/lacrimal_sac_syringing_chart.jpg"
img.save(out_path, "JPEG", quality=95)
print(f"Saved: {out_path}")
print(f"Size: {W}x{H}")
cd /home/daytona/workspace/lacrimal-chart && python create_chart.py 2>&1
ls -lh /home/daytona/workspace/lacrimal-chart/lacrimal_sac_syringing_chart.jpg
lacrimal sac syringing chart
Image · JPG
Create a PDF version for printing or archiving
python3 -c " from reportlab.lib.pagesizes import A3, landscape from reportlab.pdfgen import canvas from PIL import Image # Get image dimensions img = Image.open('/home/daytona/workspace/lacrimal-chart/lacrimal_sac_syringing_chart.jpg') img_w, img_h = img.size print(f'Image: {img_w}x{img_h}') # A3 landscape in points (1 point = 1/72 inch) page_w, page_h = landscape(A3) print(f'A3 landscape: {page_w:.1f}x{page_h:.1f} pts') out = '/home/daytona/workspace/lacrimal-chart/lacrimal_sac_syringing_chart.pdf' c = canvas.Canvas(out, pagesize=(page_w, page_h)) c.setTitle('Lacrimal Sac Syringing - Clinical Reference Chart') c.setAuthor('Orris Medical AI') c.setSubject('Ophthalmology - Lacrimal Drainage System') # Draw image to fill page exactly c.drawImage('/home/daytona/workspace/lacrimal-chart/lacrimal_sac_syringing_chart.jpg', 0, 0, width=page_w, height=page_h, preserveAspectRatio=False) c.save() print(f'PDF saved: {out}') "
ls -lh /home/daytona/workspace/lacrimal-chart/lacrimal_sac_syringing_chart.pdf
lacrimal sac syringing chart
PDF Document · PDF
Make a quick-reference pocket card (pocket-size PDF) for clinical use
python3 -c " from reportlab.lib.pagesizes import * from reportlab.lib.units import mm, cm print(f'A6: {A6[0]/mm:.1f} x {A6[1]/mm:.1f} mm') # Pocket card standard: 85.6 x 54mm (credit card) or 99x210mm (DL) or 74x105mm (A7) # Best for clinical pocket: 99x210 folded, or A6 (105x148mm) # Let's use 85x140mm - classic pocket card print(f'Credit card: 85.6 x 54 mm') print(f'Pocket card typical: 99 x 68 mm') "
~/lacrimal-chart/create_pocket_card.py
"""
Lacrimal Sac Syringing - Clinical Pocket Card
Two-sided: Front = Procedure steps + anatomy; Back = Interpretation + equipment
Card size: 99 x 148 mm (A6 portrait) - prints 2-up on A4, folds to pocket size
"""
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import mm
from reportlab.pdfgen import canvas
from reportlab.lib.colors import (
HexColor, white, black
)
from reportlab.platypus import Paragraph
from reportlab.lib.styles import ParagraphStyle
# ── Palette ────────────────────────────────────────────────────────────────
NAVY = HexColor("#0A1830")
TEAL = HexColor("#007882")
TEAL_LT = HexColor("#00B4BE")
TEAL_DARK = HexColor("#005560")
ACCENT = HexColor("#FF9832") # orange
GREEN = HexColor("#2ECC71")
RED_ = HexColor("#E74C3C")
YELLOW = HexColor("#F1C40F")
GREY_LT = HexColor("#D4EEF0")
GREY_TEXT = HexColor("#B0D4DA")
BG2 = HexColor("#0E2644") # slightly lighter navy for back
# ── Card geometry (A6) ─────────────────────────────────────────────────────
CW, CH = 105*mm, 148*mm # card width, height (A6)
PAD = 4*mm
def font(c_obj, size, bold=False):
f = "Helvetica-Bold" if bold else "Helvetica"
c_obj.setFont(f, size)
def header_band(c, x, y, w, h, bg, label, label_color=white, font_size=8):
c.setFillColor(bg)
c.roundRect(x, y, w, h, 2*mm, fill=1, stroke=0)
c.setFillColor(label_color)
font(c, font_size, bold=True)
c.drawCentredString(x + w/2, y + h/2 - font_size*0.35, label)
def section_header(c, x, y, w, text, bg=TEAL, fg=white, h=6.5*mm, font_size=7.5):
c.setFillColor(bg)
c.roundRect(x, y, w, h, 1.5*mm, fill=1, stroke=0)
c.setFillColor(fg)
font(c, font_size, bold=True)
c.drawString(x + 3*mm, y + h/2 - font_size*0.35, text)
return y + h
def bullet(c, x, y, text, color=GREY_TEXT, dot_color=TEAL_LT, size=6, indent=3.5*mm, dot_size=1.4):
c.setFillColor(dot_color)
c.circle(x + dot_size*mm, y + size*0.35, dot_size*0.7*mm, fill=1, stroke=0)
c.setFillColor(color)
font(c, size)
c.drawString(x + indent, y, text)
return y - (size*0.42*mm + 0.7*mm)
def step_row(c, x, y, num, title, detail, row_h=10*mm, num_colors=None):
colors = num_colors or [TEAL_LT, GREEN, YELLOW, ACCENT, TEAL_LT, GREEN, YELLOW]
col = colors[(num-1) % len(colors)]
# Step number circle
c.setFillColor(col)
c.circle(x + 4*mm, y + row_h/2, 3.5*mm, fill=1, stroke=0)
c.setFillColor(NAVY)
font(c, 7, bold=True)
c.drawCentredString(x + 4*mm, y + row_h/2 - 2.5, str(num))
# Title
c.setFillColor(col)
font(c, 6.8, bold=True)
c.drawString(x + 9*mm, y + row_h/2 + 0.5*mm, title)
# Detail
c.setFillColor(GREY_TEXT)
font(c, 6)
# wrap detail text manually
max_w = (CW - x - 9*mm - PAD) / (6 * 0.55) # rough char count
words = detail.split()
lines, cur = [], ""
for w in words:
test = (cur + " " + w).strip()
if len(test) <= int(max_w):
cur = test
else:
if cur: lines.append(cur)
cur = w
if cur: lines.append(cur)
dy = y + row_h/2 - 2.5*mm
for line in lines[:2]:
c.drawString(x + 9*mm, dy, line)
dy -= 3.5*mm
return y - row_h
def result_row(c, x, y, label, detail, color, row_h=9.5*mm):
# Color bar
c.setFillColor(color)
c.roundRect(x, y + 1*mm, 2.5*mm, row_h - 2*mm, 1*mm, fill=1, stroke=0)
# Label
c.setFillColor(color)
font(c, 6.5, bold=True)
c.drawString(x + 4.5*mm, y + row_h/2 + 0.8*mm, label)
# Detail
c.setFillColor(GREY_TEXT)
font(c, 6)
max_ch = int((CW - x - 4.5*mm - PAD) / (6 * 0.50))
words = detail.split()
lines, cur = [], ""
for w in words:
test = (cur + " " + w).strip()
if len(test) <= max_ch:
cur = test
else:
if cur: lines.append(cur)
cur = w
if cur: lines.append(cur)
dy = y + row_h/2 - 2.5*mm
for line in lines[:2]:
c.drawString(x + 4.5*mm, dy, line)
dy -= 3.5*mm
return y - row_h
# ══════════════════════════════════════════════════════════════════════════════
# Build PDF — two pages on A6-sized pages
# ══════════════════════════════════════════════════════════════════════════════
out = "/home/daytona/workspace/lacrimal-chart/lacrimal_pocket_card.pdf"
c = canvas.Canvas(out, pagesize=(CW, CH))
c.setTitle("Lacrimal Sac Syringing – Pocket Card")
c.setAuthor("Orris Medical AI")
c.setSubject("Quick Reference – Ophthalmology")
# ═══════════════════════════════════
# PAGE 1 — FRONT
# ═══════════════════════════════════
# Full background
c.setFillColor(NAVY)
c.rect(0, 0, CW, CH, fill=1, stroke=0)
# Top accent strip
c.setFillColor(ACCENT)
c.rect(0, CH - 1.8*mm, CW, 1.8*mm, fill=1, stroke=0)
# Header
c.setFillColor(TEAL_DARK)
c.rect(0, CH - 20*mm, CW, 19*mm, fill=1, stroke=0)
c.setFillColor(white)
font(c, 11, bold=True)
c.drawString(PAD, CH - 10.5*mm, "LACRIMAL SAC SYRINGING")
c.setFillColor(GREY_LT)
font(c, 6.5)
c.drawString(PAD, CH - 15.5*mm, "Lacrimal Irrigation | Quick Reference | Front")
# Eye glyph
c.setStrokeColor(TEAL_LT)
c.setFillColor(TEAL_LT)
c.setLineWidth(1.2)
c.ellipse(CW - 18*mm, CH - 16.5*mm, CW - 5*mm, CH - 8.5*mm, fill=0, stroke=1)
c.setFillColor(ACCENT)
c.circle(CW - 11.5*mm, CH - 12.5*mm, 3*mm, fill=1, stroke=0)
c.setFillColor(NAVY)
c.circle(CW - 11.5*mm, CH - 12.5*mm, 1.8*mm, fill=1, stroke=0)
c.setFillColor(white)
c.circle(CW - 10.5*mm, CH - 13.5*mm, 0.7*mm, fill=1, stroke=0)
y = CH - 22*mm
# ── ANATOMY STRIP ──────────────────────────────────────────────────────────
sy = section_header(c, PAD, y, CW - 2*PAD, " LACRIMAL DRAINAGE ANATOMY", TEAL, white, 6*mm, 7)
y = sy - 1*mm
# Two-column anatomy layout
mid = CW / 2
structs_l = [
("Puncta", "Posterior lid margin"),
("Ampullae", "Vertical 2 mm of canaliculus"),
("Canaliculi", "~8 mm horizontal run"),
("Common canaliculus", ">90% cases, joins sac"),
]
structs_r = [
("Rosenmüller valve", "Prevents reflux"),
("Lacrimal sac", "10–12 mm, lacrimal fossa"),
("NLD", "Opens into inferior meatus"),
("Horner muscle", "Surrounds horizontal canali."),
]
base_y = y
for term, desc in structs_l:
c.setFillColor(YELLOW)
font(c, 6, bold=True)
c.drawString(PAD, base_y, term + ":")
c.setFillColor(GREY_TEXT)
font(c, 6)
c.drawString(PAD, base_y - 3.5*mm, desc)
base_y -= 7.5*mm
base_y2 = y
for term, desc in structs_r:
c.setFillColor(YELLOW)
font(c, 6, bold=True)
c.drawString(mid, base_y2, term + ":")
c.setFillColor(GREY_TEXT)
font(c, 6)
c.drawString(mid, base_y2 - 3.5*mm, desc)
base_y2 -= 7.5*mm
y = min(base_y, base_y2) - 1.5*mm
# Divider
c.setStrokeColor(TEAL)
c.setLineWidth(0.5)
c.line(PAD, y, CW - PAD, y)
y -= 2*mm
# ── INDICATIONS & CONTRAINDICATIONS ───────────────────────────────────────
ind_w = (CW - 3*PAD) * 0.55
con_w = (CW - 3*PAD) * 0.45
# Indications header
sy2 = section_header(c, PAD, y, ind_w, " ✔ INDICATIONS", TEAL, white, 6*mm, 6.8)
y_ind = sy2 - 1*mm
inds = [
"Epiphora – diagnostic workup",
"Nasolacrimal duct obstruction",
"Pre-op DCR assessment",
"Mucocele / dacryocystitis",
"Post-op patency check",
"Identify obstruction level",
]
for ind in inds:
y_ind = bullet(c, PAD, y_ind, ind, GREY_TEXT, GREEN, 6)
# Contraindications header
sy3 = section_header(c, PAD + ind_w + PAD, y, con_w - PAD, " ✘ CONTRA.", HexColor("#8B1010"), white, 6*mm, 6.8)
y_con = sy3 - 1*mm
cons = [
"Acute dacryocystitis",
"Absent/stenosed punctum",
"Canalicular laceration",
"Suspected tumour (caution)",
]
for con in cons:
y_con = bullet(c, PAD + ind_w + PAD, y_con, con, HexColor("#FFAAAA"), RED_, 6)
y = min(y_ind, y_con) - 2*mm
# Divider
c.setStrokeColor(TEAL)
c.setLineWidth(0.5)
c.line(PAD, y, CW - PAD, y)
y -= 2*mm
# ── PROCEDURE STEPS ────────────────────────────────────────────────────────
sy4 = section_header(c, PAD, y, CW - 2*PAD, " STEP-BY-STEP PROCEDURE", TEAL_DARK, white, 6*mm, 7)
y = sy4 - 0.5*mm
steps = [
(1, "Topical LA", "Proxymetacaine 0.5% into conjunctival sac"),
(2, "Dilate Punctum", "Nettleship dilator — vertical then horizontal + lateral stretch"),
(3, "Insert Cannula", "26/27G blunt cannula on 3 ml saline syringe; enter lower punctum"),
(4, "Advance", "Rotate 90° → advance ~8 mm horizontally. Note hard/soft stop."),
(5, "Irrigate", "Gentle pressure. Patient tastes saline = patent system"),
(6, "Document", "Record stop type, reflux, flow, character of regurgitate"),
]
for num, title, detail in steps:
y = step_row(c, PAD, y, num, title, detail, row_h=9.5*mm)
if y < 10*mm:
break
# Footer
c.setFillColor(TEAL_DARK)
c.rect(0, 0, CW, 7*mm, fill=1, stroke=0)
c.setFillColor(GREY_LT)
font(c, 5.5)
c.drawCentredString(CW/2, 2.2*mm, "Kanski's Clinical Ophthalmology 10th Ed | FRONT")
# Corner fold indicator
c.setFillColor(ACCENT)
c.rect(0, 0, 7*mm, 1.5*mm, fill=1, stroke=0)
c.showPage()
# ═══════════════════════════════════
# PAGE 2 — BACK
# ═══════════════════════════════════
c.setFillColor(BG2)
c.rect(0, 0, CW, CH, fill=1, stroke=0)
# Top accent strip
c.setFillColor(TEAL_LT)
c.rect(0, CH - 1.8*mm, CW, 1.8*mm, fill=1, stroke=0)
# Header
c.setFillColor(HexColor("#0D3B52"))
c.rect(0, CH - 18*mm, CW, 17.5*mm, fill=1, stroke=0)
c.setFillColor(white)
font(c, 9.5, bold=True)
c.drawString(PAD, CH - 9.5*mm, "LACRIMAL SYRINGING")
c.setFillColor(GREY_LT)
font(c, 6.5)
c.drawString(PAD, CH - 14.5*mm, "Results Interpretation | Back")
# Back-side indicator (small "B")
c.setFillColor(ACCENT)
c.roundRect(CW - 14*mm, CH - 15*mm, 10*mm, 7*mm, 1.5*mm, fill=1, stroke=0)
c.setFillColor(NAVY)
font(c, 7, bold=True)
c.drawCentredString(CW - 9*mm, CH - 12.5*mm, "BACK")
y = CH - 20*mm
# ── HARD STOP / SOFT STOP ──────────────────────────────────────────────────
sy = section_header(c, PAD, y, CW - 2*PAD, " STOP INTERPRETATION", HexColor("#1A5276"), white, 6*mm, 7)
y = sy - 1.5*mm
half = (CW - 3*PAD) / 2
# Hard stop box
c.setFillColor(HexColor("#0D2B1A"))
c.roundRect(PAD, y - 24*mm, half, 24*mm, 2*mm, fill=1, stroke=0)
c.setStrokeColor(GREEN)
c.setLineWidth(1)
c.roundRect(PAD, y - 24*mm, half, 24*mm, 2*mm, fill=0, stroke=1)
c.setFillColor(GREEN)
font(c, 7, bold=True)
c.drawString(PAD + 3*mm, y - 4.5*mm, "HARD STOP ✔")
hard = [
"Cannula hits lacrimal bone",
"Canaliculi → PATENT",
"Obstruction is DISTAL (NLD/sac)",
"Saline → throat = patent",
"Reflux both puncta = NLD block",
]
hy = y - 9*mm
c.setFillColor(HexColor("#AAFFCC"))
font(c, 6)
for hl in hard:
c.drawString(PAD + 3*mm, hy, hl)
hy -= 3.8*mm
# Soft stop box
c.setFillColor(HexColor("#2B0D0D"))
c.roundRect(PAD + half + PAD, y - 24*mm, half, 24*mm, 2*mm, fill=1, stroke=0)
c.setStrokeColor(RED_)
c.setLineWidth(1)
c.roundRect(PAD + half + PAD, y - 24*mm, half, 24*mm, 2*mm, fill=0, stroke=1)
c.setFillColor(RED_)
font(c, 7, bold=True)
c.drawString(PAD + half + PAD + 3*mm, y - 4.5*mm, "SOFT STOP ✘")
soft = [
"Soft tissue obstruction",
"Canalicular BLOCK",
"Reflux via SAME punctum",
"= Lower canaliculus block",
"→ Consider probing/DCR",
]
sy2 = y - 9*mm
c.setFillColor(HexColor("#FFBBBB"))
font(c, 6)
for sl in soft:
c.drawString(PAD + half + PAD + 3*mm, sy2, sl)
sy2 -= 3.8*mm
y -= 26*mm
# Divider
c.setStrokeColor(TEAL)
c.setLineWidth(0.5)
c.line(PAD, y, CW - PAD, y)
y -= 2*mm
# ── RESULTS TABLE ──────────────────────────────────────────────────────────
sy3 = section_header(c, PAD, y, CW - 2*PAD, " IRRIGATION RESULTS", TEAL_DARK, white, 6*mm, 7)
y = sy3 - 1*mm
results = [
(GREEN, "PATENT", "Saline tasted in throat. No reflux."),
(TEAL_LT, "FUNCTIONAL BLOCK", "Saline reaches throat but epiphora persists → pump failure."),
(YELLOW, "NLD OBSTRUCTION", "Hard stop. Reflux both puncta. Sac may distend."),
(ACCENT, "LOWER CANAL. BLOCK","Soft stop. Reflux via LOWER punctum only."),
(RED_, "COMMON CANAL. BLOCK","Hard stop. Reflux via UPPER punctum only."),
(HexColor("#A04000"), "BILATERAL BLOCK", "Soft stop both sides."),
]
for label, short, detail in results:
y = result_row(c, PAD, y, short, detail, label, row_h=8.5*mm)
if y < 28*mm:
break
y -= 1*mm
# Divider
c.setStrokeColor(TEAL)
c.setLineWidth(0.5)
c.line(PAD, y, CW - PAD, y)
y -= 2*mm
# ── EQUIPMENT + QUICK TIPS ─────────────────────────────────────────────────
eq_w = (CW - 3*PAD) * 0.5
tip_w = (CW - 3*PAD) * 0.5
sy4 = section_header(c, PAD, y, eq_w, " EQUIPMENT", HexColor("#1A4040"), white, 5.5*mm, 6.5)
y_eq = sy4 - 1*mm
equip = [
"Proxymetacaine 0.5% LA",
"Nettleship dilator",
"26/27G blunt cannula",
"3 ml syringe + NS",
"Tissues / kidney dish",
]
for eq in equip:
y_eq = bullet(c, PAD, y_eq, eq, GREY_TEXT, TEAL_LT, 6)
sy5 = section_header(c, PAD + eq_w + PAD, y, tip_w - PAD, " KEY TIPS", HexColor("#1A3040"), white, 5.5*mm, 6.5)
y_tip = sy5 - 1*mm
tips = [
"Lateral tension on lid",
"Enter vertical → rotate 90°",
"Advance ~8 mm horizontal",
"Never force if resistance",
"Avoid in acute infection",
]
for tip in tips:
y_tip = bullet(c, PAD + eq_w + PAD, y_tip, tip, GREY_TEXT, ACCENT, 6)
# Footer
c.setFillColor(TEAL_DARK)
c.rect(0, 0, CW, 7*mm, fill=1, stroke=0)
c.setFillColor(GREY_LT)
font(c, 5.5)
c.drawCentredString(CW/2, 2.2*mm, "Kanski's Clinical Ophthalmology 10th Ed | BACK")
c.setFillColor(ACCENT)
c.rect(CW - 7*mm, 0, 7*mm, 1.5*mm, fill=1, stroke=0)
c.save()
print(f"Saved: {out}")
cd /home/daytona/workspace/lacrimal-chart && python3 create_pocket_card.py 2>&1
ls -lh /home/daytona/workspace/lacrimal-chart/lacrimal_pocket_card.pdf && python3 -c " from pypdf import PdfReader r = PdfReader('/home/daytona/workspace/lacrimal-chart/lacrimal_pocket_card.pdf') print(f'Pages: {len(r.pages)}') p = r.pages[0] print(f'Page size: {float(p.mediabox.width)/2.8346:.1f} x {float(p.mediabox.height)/2.8346:.1f} mm') "
python3 -c " from pypdf import PdfReader, PdfWriter, PageObject, Transformation from reportlab.lib.pagesizes import A4 from reportlab.lib.units import mm # A4: 210 x 297 mm = 595.3 x 841.9 pts A4W, A4H = 595.3, 841.9 # Card: 105 x 148 mm = 297.6 x 419.5 pts # Two cards side-by-side on A4 landscape: 296 x 148 mm each = A4 = 2*105 x 148 → portrait fits 2 across reader = PdfReader('/home/daytona/workspace/lacrimal-chart/lacrimal_pocket_card.pdf') front = reader.pages[0] back = reader.pages[1] CW = float(front.mediabox.width) CH = float(front.mediabox.height) # Build an A4 portrait sheet: place Front x2 on top half, Back x2 on bottom half # with cut-line guides from reportlab.pdfgen import canvas as rl_canvas from reportlab.lib.colors import HexColor import io def make_guide_page(out_path): cw = A4W ch = A4H c = rl_canvas.Canvas(out_path, pagesize=(cw, ch)) # Background white c.setFillColorRGB(1,1,1) c.rect(0,0,cw,ch,fill=1,stroke=0) # Title c.setFillColorRGB(0.04,0.09,0.19) c.setFont('Helvetica-Bold', 9) c.drawCentredString(cw/2, ch - 10*mm, 'LACRIMAL SAC SYRINGING — POCKET CARD | Print, Cut & Fold') c.setFont('Helvetica', 7) c.setFillColorRGB(0.4,0.4,0.4) c.drawCentredString(cw/2, ch - 15*mm, 'Cut along dashed lines. Each card = 105 × 148 mm (A6). Print on card stock for best results.') # Calculate positions: 2 cards per row, centered margin_x = (cw - 2*CW) / 3 row1_y = ch - 20*mm - CH # top row: fronts row2_y = row1_y - 8*mm - CH # bottom row: backs # Draw cut lines (dashed) c.setStrokeColorRGB(0.6,0.6,0.6) c.setLineWidth(0.4) c.setDash(4, 4) for col in range(2): x = margin_x + col*(CW + margin_x) for base_y in [row1_y, row2_y]: c.rect(x, base_y, CW, CH, fill=0, stroke=1) c.setDash() # reset # Corner scissors symbol c.setFont('Helvetica', 7) c.setFillColorRGB(0.5,0.5,0.5) for col in range(2): x = margin_x + col*(CW + margin_x) for base_y in [row1_y, row2_y]: c.drawString(x + 1, base_y + CH + 1, '✂') c.drawString(x + CW - 8, base_y + CH + 1, '✂') # Labels c.setFillColorRGB(0.04,0.09,0.19) c.setFont('Helvetica-Bold', 7) for col in range(2): x = margin_x + col*(CW + margin_x) c.drawCentredString(x + CW/2, row1_y - 4*mm, 'FRONT') c.drawCentredString(x + CW/2, row2_y - 4*mm, 'BACK') c.save() return row1_y, row2_y, margin_x row1_y, row2_y, margin_x = make_guide_page('/tmp/guide.pdf') # Now merge: overlay the card pages onto the guide from pypdf import PdfWriter, PdfReader, Transformation guide_reader = PdfReader('/tmp/guide.pdf') writer = PdfWriter() # We need to stamp the card pages onto the guide page guide_page = guide_reader.pages[0] # For each card position, merge front/back # Clone guide page and add card content via merge_page import copy def stamp_card(base_page, card_page, tx, ty): page = copy.copy(base_page) card = copy.copy(card_page) card.add_transformation(Transformation().translate(tx, ty)) page.merge_page(card) return page # Build final page: 2 fronts + 2 backs on the guide # Use the guide as base and stamp cards page_out = copy.copy(guide_page) for col in range(2): x = margin_x + col*(float(front.mediabox.width) + margin_x) # Front f = copy.copy(front) f.add_transformation(Transformation().translate(x, row1_y)) page_out.merge_page(f) # Back bk = copy.copy(back) bk.add_transformation(Transformation().translate(x, row2_y)) page_out.merge_page(bk) writer.add_page(page_out) out = '/home/daytona/workspace/lacrimal-chart/lacrimal_pocket_card_printsheet_A4.pdf' with open(out, 'wb') as f: writer.write(f) print(f'Saved: {out}') import os print(f'Size: {os.path.getsize(out)/1024:.1f} KB') "
lacrimal pocket card
PDF Document · PDF
lacrimal pocket card printsheet A4
PDF Document · PDF
| FRONT | BACK |
|---|---|
| Anatomy of lacrimal drainage (8 structures) | Hard Stop vs Soft Stop (color-coded boxes) |
| Indications (6 items) | Irrigation results table (6 scenarios) |
| Contraindications (4 items) | Equipment checklist |
| 6-step procedure with color-coded circles | 5 key technique tips |