Inguinal canal

| Wall | Formed By |
|---|---|
| Anterior wall | Aponeurosis of external oblique (entire length); reinforced laterally by lower fibers of internal oblique (over the deep ring) |
| Posterior wall | Transversalis fascia (entire length); reinforced medially by the conjoint tendon (inguinal falx) in its medial one-third |
| Roof (superior wall) | Arching fibers of transversus abdominis and internal oblique muscles |
| Floor (inferior wall) | Medial half of the inguinal ligament (rolled-under free margin of external oblique aponeurosis); lacunar ligament reinforces the medial part |

| Layer | Derived From |
|---|---|
| External spermatic fascia | Aponeurosis of external oblique |
| Cremasteric fascia + cremaster muscle | Internal oblique + transversus abdominis |
| Internal spermatic fascia | Transversalis fascia |

Create a detailed anatomy reference PDF on the inguinal canal
ls -la /home/daytona/workspace/ 2>/dev/null || echo "workspace empty or missing"
mkdir -p /home/daytona/workspace/inguinal-canal-pdf/images && cd /home/daytona/workspace/inguinal-canal-pdf && curl -s -o images/fig1_overview.png "https://cdn.orris.care/cdss_images/98ec6b7670bd9a2245a55c4128de139ea1e463497ff5c2e133ec9fb0659c7f5a.png" && curl -s -o images/fig2_sagittal.png "https://cdn.orris.care/cdss_images/2859a5fff5d5b81ab48b2717f05b805b900a5e7de2ef04dd62cd6f1a0d3feb1b.png" && curl -s -o images/fig3_indirect_hernia.png "https://cdn.orris.care/cdss_images/35b74c93040018a895a228d5a83709af0f2285df624e1848c57c162ecc84de7a.png" && curl -s -o images/fig4_deep_ring.png "https://cdn.orris.care/cdss_images/a8be295d6de2a6f38485d1031bc156ac702af258bdad6e70e294888d295c83d1.png" && curl -s -o images/fig5_superficial_ring.png "https://cdn.orris.care/cdss_images/04d2f79a4673a1735a21fcc12d047f985c84b8a7ddb2c811adc16c30360693ba.png" && curl -s -o images/fig6_walls.png "https://cdn.orris.care/cdss_images/2a97352b6c9e45b4b9f3ac0e5c7be07cfa18cf4e7a13a31b3cb5c48ba7a9b80.png" && ls -lh images/
cd /home/daytona/workspace/inguinal-canal-pdf && curl -s -o images/fig6_direct_hernia.png "https://cdn.orris.care/cdss_images/873859c613663d2153c5443ee8c82716a7b15e006292870d1b05d49a3f5243c6.png" && curl -s -o images/fig7_triangle.png "https://cdn.orris.care/cdss_images/ef8773481b6b8631b07daf181475dc5333c0dba48416eb60f2016af499e9e46b.png" && ls -lh images/
~/inguinal-canal-pdf/generate_pdf.py
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.colors import HexColor, black, white
from reportlab.lib.units import cm, mm
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
Image, HRFlowable, PageBreak, KeepTogether
)
from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_JUSTIFY
from reportlab.platypus import Flowable
from reportlab.pdfgen import canvas
from reportlab.lib import colors
import os
# ── Colours ────────────────────────────────────────────────────────────────
C_NAVY = HexColor("#1a2e4a")
C_BLUE = HexColor("#2563a8")
C_LIGHT_BLUE= HexColor("#dbeafe")
C_TEAL = HexColor("#0d6e6e")
C_LIGHT_TEAL= HexColor("#ccf0f0")
C_AMBER = HexColor("#b45309")
C_AMBER_BG = HexColor("#fef3c7")
C_RED = HexColor("#b91c1c")
C_RED_BG = HexColor("#fee2e2")
C_GREEN = HexColor("#166534")
C_GREEN_BG = HexColor("#dcfce7")
C_GRAY_LINE = HexColor("#cbd5e1")
C_BODY_TEXT = HexColor("#1e293b")
C_CAPTION = HexColor("#475569")
C_HEADER_BG = HexColor("#1a2e4a")
C_TABLE_HDR = HexColor("#2563a8")
C_TABLE_ALT = HexColor("#f1f5f9")
BASE_DIR = "/home/daytona/workspace/inguinal-canal-pdf"
IMG_DIR = os.path.join(BASE_DIR, "images")
OUT_PATH = os.path.join(BASE_DIR, "Inguinal_Canal_Anatomy_Reference.pdf")
W, H = A4
MARGIN = 2*cm
# ── Page template with header/footer ───────────────────────────────────────
class HeaderFooterCanvas(canvas.Canvas):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._saved_page_states = []
def showPage(self):
self._saved_page_states.append(dict(self.__dict__))
self._startPage()
def save(self):
num_pages = len(self._saved_page_states)
for state in self._saved_page_states:
self.__dict__.update(state)
self.draw_header_footer(num_pages)
canvas.Canvas.showPage(self)
canvas.Canvas.save(self)
def draw_header_footer(self, num_pages):
page_num = self._pageNumber
w, h = A4
# -- header bar (skip on cover page 1)
if page_num > 1:
self.setFillColor(C_NAVY)
self.rect(0, h - 1.5*cm, w, 1.5*cm, fill=1, stroke=0)
self.setFillColor(white)
self.setFont("Helvetica-Bold", 9)
self.drawString(MARGIN, h - 0.95*cm, "INGUINAL CANAL | ANATOMY REFERENCE")
self.setFont("Helvetica", 8)
self.drawRightString(w - MARGIN, h - 0.95*cm, "Gray's Anatomy · Bailey & Love · THIEME Atlas")
# -- footer rule
self.setStrokeColor(C_GRAY_LINE)
self.setLineWidth(0.5)
self.line(MARGIN, 1.5*cm, w - MARGIN, 1.5*cm)
# -- page number
self.setFillColor(C_CAPTION)
self.setFont("Helvetica", 8)
if page_num > 1:
self.drawCentredString(w/2, 0.8*cm, f"Page {page_num} of {num_pages}")
# -- footer text
self.setFont("Helvetica-Oblique", 7)
self.setFillColor(C_CAPTION)
self.drawString(MARGIN, 0.8*cm,
"Compiled from Gray's Anatomy for Students · Bailey & Love's Surgery 28e · THIEME Atlas of Anatomy")
# ── Style sheet ────────────────────────────────────────────────────────────
styles = getSampleStyleSheet()
def make_style(name, parent="Normal", **kw):
s = ParagraphStyle(name, parent=styles[parent], **kw)
return s
S_title = make_style("S_title", fontSize=30, leading=36, textColor=white,
fontName="Helvetica-Bold", alignment=TA_CENTER)
S_subtitle = make_style("S_subtitle", fontSize=13, leading=18, textColor=HexColor("#93c5fd"),
fontName="Helvetica-Oblique", alignment=TA_CENTER)
S_cover_note = make_style("S_cover_note", fontSize=9, leading=14, textColor=HexColor("#cbd5e1"),
fontName="Helvetica", alignment=TA_CENTER)
S_h1 = make_style("S_h1", fontSize=15, leading=20, textColor=white,
fontName="Helvetica-Bold", alignment=TA_LEFT,
spaceBefore=6, spaceAfter=4)
S_h2 = make_style("S_h2", fontSize=11, leading=16, textColor=C_BLUE,
fontName="Helvetica-Bold", alignment=TA_LEFT,
spaceBefore=10, spaceAfter=3)
S_h3 = make_style("S_h3", fontSize=10, leading=14, textColor=C_TEAL,
fontName="Helvetica-Bold", alignment=TA_LEFT,
spaceBefore=6, spaceAfter=2)
S_body = make_style("S_body", fontSize=9.5, leading=15, textColor=C_BODY_TEXT,
fontName="Helvetica", alignment=TA_JUSTIFY,
spaceBefore=2, spaceAfter=2)
S_bullet = make_style("S_bullet", fontSize=9.5, leading=14, textColor=C_BODY_TEXT,
fontName="Helvetica", alignment=TA_LEFT,
leftIndent=14, bulletIndent=4,
spaceBefore=1, spaceAfter=1)
S_caption = make_style("S_caption", fontSize=8, leading=11, textColor=C_CAPTION,
fontName="Helvetica-Oblique", alignment=TA_CENTER,
spaceBefore=2, spaceAfter=6)
S_callout = make_style("S_callout", fontSize=9.5, leading=14, textColor=C_BODY_TEXT,
fontName="Helvetica", alignment=TA_JUSTIFY,
leftIndent=10, rightIndent=10,
spaceBefore=2, spaceAfter=2)
S_tbl_hdr = make_style("S_tbl_hdr", fontSize=9, leading=12, textColor=white,
fontName="Helvetica-Bold", alignment=TA_CENTER)
S_tbl_cell = make_style("S_tbl_cell", fontSize=9, leading=13, textColor=C_BODY_TEXT,
fontName="Helvetica", alignment=TA_LEFT)
S_tbl_cell_c = make_style("S_tbl_cell_c", fontSize=9, leading=13, textColor=C_BODY_TEXT,
fontName="Helvetica", alignment=TA_CENTER)
S_mnemonic = make_style("S_mnemonic", fontSize=11, leading=16, textColor=C_AMBER,
fontName="Helvetica-Bold", alignment=TA_CENTER)
# ── Helper Flowables ───────────────────────────────────────────────────────
class SectionHeader(Flowable):
"""Full-width coloured section header bar."""
def __init__(self, text, color=C_NAVY, text_color=white, height=22):
super().__init__()
self.text = text
self.color = color
self.text_color = text_color
self.height = height
self.width = W - 2*MARGIN
def wrap(self, aw, ah):
return self.width, self.height + 4
def draw(self):
c = self.canv
c.setFillColor(self.color)
c.roundRect(0, 0, self.width, self.height, 4, fill=1, stroke=0)
c.setFillColor(self.text_color)
c.setFont("Helvetica-Bold", 11)
c.drawString(10, 6, self.text)
class ColorBox(Flowable):
"""Coloured background box for callouts."""
def __init__(self, content_list, bg_color, border_color, width=None):
super().__init__()
self.content_list = content_list
self.bg_color = bg_color
self.border_color = border_color
self._width = width or (W - 2*MARGIN)
def wrap(self, aw, ah):
total_h = 8
for item in self.content_list:
_, h = item.wrap(self._width - 20, ah)
total_h += h + 3
self._height = total_h + 8
return self._width, self._height
def draw(self):
c = self.canv
c.setFillColor(self.bg_color)
c.setStrokeColor(self.border_color)
c.setLineWidth(1)
c.roundRect(0, 0, self._width, self._height, 5, fill=1, stroke=1)
y = self._height - 8
for item in self.content_list:
item.canv = c
_, h = item.wrap(self._width - 20, 9999)
item.drawOn(c, 10, y - h)
y -= h + 3
def bullet(text):
return Paragraph(f"<bullet>•</bullet> {text}", S_bullet)
def body(text):
return Paragraph(text, S_body)
def h2(text):
return Paragraph(text, S_h2)
def h3(text):
return Paragraph(text, S_h3)
def caption(text):
return Paragraph(text, S_caption)
def spacer(h=6):
return Spacer(1, h)
def rule(color=C_GRAY_LINE):
return HRFlowable(width="100%", thickness=0.5, color=color, spaceAfter=4, spaceBefore=4)
def img(filename, width, caption_text="", max_height=None):
"""Return [Image, caption_para] list; skips if file missing/too small."""
path = os.path.join(IMG_DIR, filename)
if not os.path.exists(path) or os.path.getsize(path) < 500:
return []
im = Image(path, width=width)
if max_height and im.imageHeight * (width / im.imageWidth) > max_height:
scale = max_height / im.imageHeight
im = Image(path, width=im.imageWidth * scale, height=max_height)
items = [im]
if caption_text:
items.append(caption(caption_text))
return items
def table(data, col_widths, row_colors=None, header_color=C_TABLE_HDR):
"""Build a styled table. data[0] is the header row."""
tbl = Table(data, colWidths=col_widths)
n_rows = len(data)
style_cmds = [
("BACKGROUND", (0,0), (-1,0), header_color),
("TEXTCOLOR", (0,0), (-1,0), white),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,0), 9),
("ALIGN", (0,0), (-1,0), "CENTER"),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("FONTNAME", (0,1), (-1,-1), "Helvetica"),
("FONTSIZE", (0,1), (-1,-1), 9),
("ROWBACKGROUND",(0,1),(-1,-1), [white, C_TABLE_ALT]),
("GRID", (0,0), (-1,-1), 0.4, C_GRAY_LINE),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING",(0,0),(-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 6),
("RIGHTPADDING",(0,0),(-1,-1), 6),
("ROWBACKGROUND",(0,1),(-1,-1), [white, C_TABLE_ALT]),
]
tbl.setStyle(TableStyle(style_cmds))
return tbl
# ── Reusable cell paragraph ────────────────────────────────────────────────
def tc(text, bold=False, center=False):
sty = S_tbl_cell_c if center else S_tbl_cell
fn = "Helvetica-Bold" if bold else "Helvetica"
s = ParagraphStyle("_tc", parent=sty, fontName=fn)
return Paragraph(text, s)
# ══════════════════════════════════════════════════════════════════════════════
# BUILD DOCUMENT
# ══════════════════════════════════════════════════════════════════════════════
story = []
IW = W - 2*MARGIN # inner width
# ─────────────────────────────────────────────────────────────────────────────
# COVER PAGE
# ─────────────────────────────────────────────────────────────────────────────
class CoverPage(Flowable):
def wrap(self, aw, ah):
return W, H
def draw(self):
c = self.canv
# background gradient simulation (two rects)
c.setFillColor(C_NAVY)
c.rect(0, 0, W, H, fill=1, stroke=0)
c.setFillColor(HexColor("#0f1d30"))
c.rect(0, 0, W, H*0.45, fill=1, stroke=0)
# decorative circles
c.setFillColor(HexColor("#1e3a5f"))
c.setStrokeColor(HexColor("#2563a8"))
c.setLineWidth(0.5)
c.circle(W*0.85, H*0.78, 90, fill=1, stroke=1)
c.circle(W*0.12, H*0.25, 60, fill=1, stroke=0)
# accent bar
c.setFillColor(C_BLUE)
c.rect(MARGIN, H*0.52, IW, 4, fill=1, stroke=0)
# title
c.setFillColor(white)
c.setFont("Helvetica-Bold", 36)
c.drawCentredString(W/2, H*0.72, "INGUINAL CANAL")
c.setFillColor(HexColor("#93c5fd"))
c.setFont("Helvetica-Bold", 16)
c.drawCentredString(W/2, H*0.64, "Comprehensive Anatomy Reference")
# subtitle
c.setFillColor(HexColor("#cbd5e1"))
c.setFont("Helvetica-Oblique", 11)
c.drawCentredString(W/2, H*0.57,
"Walls · Contents · Rings · Hernias · Clinical Correlates")
# bottom metadata
c.setFillColor(HexColor("#64748b"))
c.setFont("Helvetica", 8)
c.drawCentredString(W/2, H*0.14,
"Sources: Gray's Anatomy for Students · Bailey & Love's Short Practice of Surgery (28e)")
c.drawCentredString(W/2, H*0.11,
"THIEME Atlas of General Anatomy · Sabiston Textbook of Surgery")
c.setFillColor(C_BLUE)
c.setFont("Helvetica-Bold", 8)
c.drawCentredString(W/2, H*0.07, "ORRIS MEDICAL REFERENCE · June 2026")
story.append(CoverPage())
story.append(PageBreak())
# ─────────────────────────────────────────────────────────────────────────────
# TABLE OF CONTENTS (simple)
# ─────────────────────────────────────────────────────────────────────────────
story.append(SectionHeader("TABLE OF CONTENTS"))
story.append(spacer(10))
toc_items = [
("1", "Overview & Orientation", "3"),
("2", "Openings: Deep & Superficial Inguinal Rings", "3"),
("3", "Walls of the Inguinal Canal", "4"),
("4", "Contents (Male & Female)", "5"),
("5", "Layers of the Spermatic Cord", "6"),
("6", "Neurovascular Relations", "6"),
("7", "Inguinal Hernias", "7"),
("8", "Hesselbach's (Inguinal) Triangle", "8"),
("9", "Cremasteric Reflex", "9"),
("10", "Embryology & Processus Vaginalis", "9"),
("11", "Clinical Summary & Mnemonics", "10"),
]
toc_data = [[tc("#", bold=True, center=True), tc("Section", bold=True), tc("Page", bold=True, center=True)]]
for num, title, page in toc_items:
toc_data.append([tc(num, center=True), tc(title), tc(page, center=True)])
story.append(table(toc_data, [1*cm, 12.5*cm, 2*cm]))
story.append(PageBreak())
# ─────────────────────────────────────────────────────────────────────────────
# SECTION 1 – OVERVIEW
# ─────────────────────────────────────────────────────────────────────────────
story.append(SectionHeader("1. OVERVIEW & ORIENTATION"))
story.append(spacer(8))
story.append(body(
"The <b>inguinal canal</b> is a slit-like oblique passage in the lower anterior abdominal wall, "
"approximately <b>4–6 cm long</b> in adults. It runs downward and medially just above and parallel "
"to the lower half of the inguinal ligament. The canal begins posteriorly at the "
"<b>deep (internal) inguinal ring</b> and ends anteriorly at the <b>superficial (external) "
"inguinal ring</b>. During fetal life it transmits the descending testis; after birth it contains "
"the spermatic cord in males and the round ligament of the uterus in females."
))
story.append(spacer(6))
for it in img("fig1_overview.png", IW,
"Fig. 1 – Inguinal Canal, anterior view. The deep and superficial inguinal rings, "
"inguinal ligament, and spermatic cord are labelled. (Gray's Anatomy for Students)"):
story.append(it)
story.append(spacer(4))
# Key facts box
kf_items = [
Paragraph("<b>Key facts at a glance</b>", ParagraphStyle("kfh", parent=S_callout,
fontName="Helvetica-Bold", textColor=C_BLUE, fontSize=10)),
]
facts = [
"Length: 4–6 cm",
"Direction: oblique, downward and medial",
"Runs just above the inguinal (Poupart's) ligament",
"Deep ring is lateral; superficial ring is medial",
"Most common site of abdominal wall herniation",
"Canal remains patent throughout life, especially in males",
]
for f in facts:
kf_items.append(Paragraph(f"<bullet>•</bullet> {f}", S_callout))
story.append(ColorBox(kf_items, C_LIGHT_BLUE, C_BLUE))
story.append(spacer(8))
# ─────────────────────────────────────────────────────────────────────────────
# SECTION 2 – OPENINGS
# ─────────────────────────────────────────────────────────────────────────────
story.append(SectionHeader("2. OPENINGS: DEEP & SUPERFICIAL INGUINAL RINGS"))
story.append(spacer(8))
story.append(h2("Deep (Internal) Inguinal Ring"))
story.append(body(
"The deep inguinal ring is the beginning of the canal and the internal opening of the abdominal wall "
"to the inguinal canal. Its key anatomical relationships are:"
))
for b_text in [
"<b>Location:</b> midpoint between the <b>anterior superior iliac spine (ASIS)</b> and the <b>pubic "
"symphysis</b>, just above the inguinal ligament",
"<b>Lateral relation:</b> lies <b>immediately lateral</b> to the inferior epigastric vessels "
"(key landmark for distinguishing indirect from direct hernias)",
"<b>Structure:</b> not a simple defect — it is the origin of a tubular evagination of "
"<b>transversalis fascia</b> that becomes the <i>internal spermatic fascia</i>",
"<b>Muscular guard:</b> lower fibres of the internal oblique and transversus abdominis arch over the "
"ring from lateral to medial, forming the conjoint tendon — providing dynamic protection against herniation",
]:
story.append(bullet(b_text))
story.append(spacer(6))
# Deep ring image
for it in img("fig4_deep_ring.png", IW*0.72,
"Fig. 2 – Deep inguinal ring and transversalis fascia. The inferior epigastric vessels "
"lie just medial to the ring. (Gray's Anatomy for Students)"):
story.append(it)
story.append(spacer(8))
story.append(h2("Superficial (External) Inguinal Ring"))
story.append(body(
"The superficial inguinal ring is the external opening of the canal, located superior to the "
"pubic tubercle:"
))
for b_text in [
"<b>Structure:</b> triangular opening in the <b>aponeurosis of the external oblique muscle</b>",
"<b>Apex:</b> points superolaterally, held together by <b>intercrural fibres</b> preventing widening",
"<b>Medial crus:</b> attached to the pubic symphysis",
"<b>Lateral crus:</b> attached to the pubic tubercle",
"<b>Evagination:</b> the aponeurosis continues over the spermatic cord as the <i>external spermatic fascia</i>",
"<b>Size:</b> larger in males than females; can admit the examiner's finger in an inguinal hernia examination",
]:
story.append(bullet(b_text))
story.append(spacer(6))
for it in img("fig5_superficial_ring.png", IW*0.72,
"Fig. 3 – Superficial inguinal ring in the aponeurosis of external oblique. The medial "
"and lateral crura and intercrural fibres are shown. (Gray's Anatomy for Students)"):
story.append(it)
story.append(PageBreak())
# ─────────────────────────────────────────────────────────────────────────────
# SECTION 3 – WALLS
# ─────────────────────────────────────────────────────────────────────────────
story.append(SectionHeader("3. WALLS OF THE INGUINAL CANAL"))
story.append(spacer(8))
story.append(body(
"The inguinal canal has four walls (anterior, posterior, roof/superior, floor/inferior), "
"each formed by a different layer of the anterior abdominal wall. Understanding the walls is "
"essential for understanding hernia types and surgical repair."
))
story.append(spacer(8))
walls_data = [
[tc("WALL", bold=True, center=True),
tc("FORMED BY", bold=True, center=True),
tc("NOTES", bold=True, center=True)],
[tc("Anterior", bold=True),
tc("Aponeurosis of external oblique (full length); reinforced laterally by lower fibres of internal oblique"),
tc("The internal oblique reinforcement covers the deep ring — protecting this potential weak point")],
[tc("Posterior", bold=True),
tc("Transversalis fascia (full length); reinforced medially by the conjoint tendon (inguinal falx)"),
tc("Conjoint tendon = fused insertion of transversus abdominis + internal oblique into pubic crest")],
[tc("Roof\n(Superior)", bold=True),
tc("Arching fibres of transversus abdominis and internal oblique muscles"),
tc("These fibres arch from their lateral origin on the inguinal ligament to medial insertion as the conjoint tendon")],
[tc("Floor\n(Inferior)", bold=True),
tc("Medial half of the inguinal ligament (rolled-under free margin of external oblique aponeurosis)"),
tc("Lacunar ligament reinforces the medial part of the floor")],
]
story.append(table(walls_data, [2.5*cm, 6.5*cm, 5.0*cm]))
story.append(spacer(8))
# Mnemonic for walls
mem_items = [
Paragraph("<b>Mnemonic for the Walls: \"MAT Floor\"</b>", ParagraphStyle("memh",
parent=S_callout, fontName="Helvetica-Bold", textColor=C_AMBER, fontSize=10)),
Paragraph("<b>M</b>uscles (internal oblique) → Anterior reinforcement", S_callout),
Paragraph("<b>A</b>poneurosis of external oblique → Anterior wall (full length)", S_callout),
Paragraph("<b>T</b>ransversalis fascia → Posterior wall (full length)", S_callout),
Paragraph("<b>Floor</b> = Inguinal ligament (medial half)", S_callout),
]
story.append(ColorBox(mem_items, C_AMBER_BG, C_AMBER))
story.append(spacer(8))
for it in img("fig2_sagittal.png", IW,
"Fig. 4 – Sagittal section through the inguinal canal. The spermatic cord lies "
"within the canal. All four walls and their relationships to fascial layers are visible. "
"(THIEME Atlas of General Anatomy)"):
story.append(it)
story.append(PageBreak())
# ─────────────────────────────────────────────────────────────────────────────
# SECTION 4 – CONTENTS
# ─────────────────────────────────────────────────────────────────────────────
story.append(SectionHeader("4. CONTENTS OF THE INGUINAL CANAL"))
story.append(spacer(8))
story.append(h2("Male Contents"))
story.append(body(
"In males the inguinal canal transmits the <b>spermatic cord</b>, which consists of the following "
"structures bundled within fascial coverings:"
))
male_contents = [
[tc("STRUCTURE", bold=True, center=True), tc("FUNCTION", bold=True, center=True), tc("ORIGIN", bold=True, center=True)],
[tc("Vas deferens (ductus deferens)"), tc("Conducts spermatozoa"), tc("Epididymis → ejaculatory duct")],
[tc("Testicular artery"), tc("Arterial supply to testis"), tc("Abdominal aorta (L2)")],
[tc("Pampiniform venous plexus"), tc("Venous drainage of testis; forms testicular vein at deep ring"), tc("Testis")],
[tc("Testicular lymphatics"), tc("Drain to para-aortic nodes (not inguinal nodes)"), tc("Testis")],
[tc("Autonomic nerve fibres"), tc("Vasomotor; pain fibres from testis (T10–11)"), tc("Renal/aortic plexus")],
[tc("Genital br. of genitofemoral n."), tc("Motor to cremaster; sensory to scrotal skin"), tc("L1/L2")],
[tc("Cremasteric artery"), tc("Supply to cremaster muscle"), tc("Inferior epigastric artery")],
[tc("Artery to vas deferens"), tc("Supply to vas"), tc("Inferior vesical artery")],
]
story.append(table(male_contents, [4.5*cm, 5.5*cm, 4.0*cm]))
story.append(spacer(4))
story.append(body(
"<b>Additionally in both sexes:</b> the <b>ilioinguinal nerve (L1)</b> runs through part of the canal "
"on the spermatic cord and exits through the superficial ring to supply sensory innervation to the "
"pubic symphysis, anterior scrotum/labia majora, and medial thigh."
))
story.append(spacer(10))
story.append(h2("Female Contents"))
female_items = [
"<b>Round ligament of the uterus</b> — passes through the entire canal and ends in the labia majora (fatty tissue)",
"<b>Genital branch of the genitofemoral nerve</b> — accompanies the round ligament; sensory to labia majora",
"<b>Ilioinguinal nerve</b> — as in males, runs through the lower canal and exits the superficial ring",
"<b>Lymphatic vessels</b> — from fundus of uterus along the round ligament to inguinal lymph nodes",
]
for item in female_items:
story.append(bullet(item))
story.append(spacer(6))
fn_box = [
Paragraph("<b>Clinical note:</b> Lymphatic drainage of the testis follows the testicular artery "
"to <b>para-aortic nodes</b> at L1–L2. This is why testicular cancer spreads to "
"retroperitoneal nodes, NOT to inguinal nodes. Scrotal skin, however, drains to "
"<b>superficial inguinal nodes</b>. This distinction is clinically important.",
S_callout),
]
story.append(ColorBox(fn_box, C_GREEN_BG, C_GREEN))
story.append(PageBreak())
# ─────────────────────────────────────────────────────────────────────────────
# SECTION 5 – SPERMATIC CORD LAYERS
# ─────────────────────────────────────────────────────────────────────────────
story.append(SectionHeader("5. LAYERS OF THE SPERMATIC CORD"))
story.append(spacer(8))
story.append(body(
"As the spermatic cord forms and traverses the inguinal canal, it acquires three concentric fascial "
"coverings, each derived from a different abdominal wall layer. The coverings are acquired in sequence "
"from outside in as the structures pass through the canal:"
))
story.append(spacer(6))
layers_data = [
[tc("LAYER (outside → in)", bold=True, center=True),
tc("DERIVED FROM", bold=True, center=True),
tc("WHERE ACQUIRED", bold=True, center=True)],
[tc("External spermatic fascia", bold=True),
tc("Aponeurosis of external oblique"),
tc("At the superficial inguinal ring")],
[tc("Cremasteric fascia + cremaster muscle", bold=True),
tc("Internal oblique (+ transversus abdominis fibres)"),
tc("As cord passes through the canal, opposite the internal oblique")],
[tc("Internal spermatic fascia", bold=True),
tc("Transversalis fascia"),
tc("At the deep inguinal ring")],
]
story.append(table(layers_data, [4.0*cm, 6.0*cm, 4.0*cm]))
story.append(spacer(6))
story.append(body(
"Deepest of all — not a cord covering but the innermost layer — is the <b>tunica vaginalis</b> "
"(derived from peritoneum), which surrounds only the testis and epididymis within the scrotum."
))
story.append(spacer(6))
cov_box = [
Paragraph("<b>Mnemonic (outermost → innermost):</b>", ParagraphStyle("covh",
parent=S_callout, fontName="Helvetica-Bold", textColor=C_TEAL, fontSize=10)),
Paragraph("<b>\"Every Cremaster Is Important\"</b>", ParagraphStyle("covm",
parent=S_callout, fontName="Helvetica-Bold", textColor=C_TEAL, fontSize=12,
alignment=TA_CENTER)),
Paragraph("<b>E</b>xternal spermatic fascia → <b>C</b>remasteric fascia → "
"<b>I</b>nternal spermatic fascia", S_callout),
]
story.append(ColorBox(cov_box, C_LIGHT_TEAL, C_TEAL))
story.append(PageBreak())
# ─────────────────────────────────────────────────────────────────────────────
# SECTION 6 – NEUROVASCULAR RELATIONS
# ─────────────────────────────────────────────────────────────────────────────
story.append(SectionHeader("6. NEUROVASCULAR RELATIONS"))
story.append(spacer(8))
story.append(h2("Nerves"))
nerves_data = [
[tc("NERVE", bold=True, center=True),
tc("ROOT", bold=True, center=True),
tc("COURSE THROUGH CANAL", bold=True, center=True),
tc("DISTRIBUTION", bold=True, center=True)],
[tc("Ilioinguinal nerve"),
tc("L1", center=True),
tc("Enters canal by piercing internal oblique; exits through superficial ring"),
tc("Pubic symphysis, anterior scrotum/labia majora, medial thigh")],
[tc("Genital branch of genitofemoral nerve"),
tc("L1/L2", center=True),
tc("Enters canal through deep ring; runs with spermatic cord"),
tc("Motor: cremaster muscle. Sensory: scrotal skin / labia majora")],
[tc("Iliohypogastric nerve"),
tc("T12/L1", center=True),
tc("Runs superior to canal; pierces external oblique aponeurosis above superficial ring"),
tc("Suprapubic and lateral gluteal skin")],
]
story.append(table(nerves_data, [3.5*cm, 1.5*cm, 5.0*cm, 4.0*cm]))
story.append(spacer(8))
story.append(h2("Vascular Relationships"))
story.append(body(
"The <b>inferior epigastric vessels</b> (from the external iliac artery and vein) run just "
"<b>medial to the deep inguinal ring</b>, ascending in the extraperitoneal tissue to enter the "
"rectus sheath. Their position is the single most important landmark in the inguinal region:"
))
for b_text in [
"Indirect inguinal hernia neck: <b>lateral</b> to the inferior epigastric vessels",
"Direct inguinal hernia neck: <b>medial</b> to the inferior epigastric vessels",
"The vessels are the lateral boundary of Hesselbach's triangle",
"Damage during laparoscopic repair → haematoma ('port-site haematoma')",
]:
story.append(bullet(b_text))
story.append(PageBreak())
# ─────────────────────────────────────────────────────────────────────────────
# SECTION 7 – INGUINAL HERNIAS
# ─────────────────────────────────────────────────────────────────────────────
story.append(SectionHeader("7. INGUINAL HERNIAS"))
story.append(spacer(8))
story.append(body(
"An inguinal hernia is the protrusion of a peritoneal sac — with or without abdominal contents — "
"through a weakened part of the abdominal wall in the groin. Two types exist based on the route "
"through the canal:"
))
story.append(spacer(8))
story.append(h2("Indirect Inguinal Hernia"))
# Two-column layout via table
indirect_text = [
bullet("<b>Most common</b> type of inguinal hernia; far more common in males"),
bullet("Peritoneal sac enters through the <b>deep inguinal ring</b> — <b>lateral</b> to inferior epigastric vessels"),
bullet("<b>Congenital basis:</b> persistence of the processus vaginalis"),
bullet("Follows the oblique course of the spermatic cord through the canal"),
bullet("Can traverse the full canal and descend into the scrotum (scrotal hernia) or labia majora"),
bullet("Sac acquires all <b>three coverings</b> of the spermatic cord"),
bullet("More common in young males and infants"),
body(""),
]
for item in indirect_text:
story.append(item)
story.append(spacer(6))
for it in img("fig3_indirect_hernia.png", IW*0.6,
"Fig. 5 – Indirect inguinal hernia. The peritoneal sac (red arrow) follows the spermatic "
"cord through the deep ring, along the canal, through the superficial ring, and into "
"the scrotum. The testis is shown at the base. (Gray's Anatomy for Students)"):
story.append(it)
story.append(spacer(10))
story.append(h2("Direct Inguinal Hernia"))
direct_items = [
bullet("<b>Always acquired;</b> results from weakness/atrophy of the posterior wall musculature"),
bullet("Bulges directly through the <b>posterior wall</b> of the canal — <b>medial</b> to inferior epigastric vessels"),
bullet("Passes through <b>Hesselbach's triangle</b>"),
bullet("Does <b>not</b> traverse the full length of the canal"),
bullet("May exit through the superficial ring (acquiring external spermatic fascia only)"),
bullet("More common in older men; rarely in females"),
bullet("Less risk of strangulation than indirect (wide neck)"),
]
for item in direct_items:
story.append(item)
story.append(spacer(8))
for it in img("fig6_direct_hernia.png", IW*0.6,
"Fig. 6 – Direct inguinal hernia. The peritoneal bulge passes medial to the inferior "
"epigastric vessels through Hesselbach's triangle. (Gray's Anatomy for Students)"):
story.append(it)
story.append(spacer(8))
story.append(h2("Comparison Table: Indirect vs. Direct"))
compare_data = [
[tc("FEATURE", bold=True, center=True),
tc("INDIRECT", bold=True, center=True),
tc("DIRECT", bold=True, center=True)],
[tc("Entry point"), tc("Deep inguinal ring"), tc("Posterior wall of canal")],
[tc("Relation to inf. epigastric vessels"), tc("Lateral"), tc("Medial")],
[tc("Aetiology"), tc("Congenital (patent processus vaginalis)"), tc("Acquired (muscle weakness)")],
[tc("Age group"), tc("Any age; common in infants"), tc("Older adults")],
[tc("Can reach scrotum?"), tc("Yes (scrotal hernia)"), tc("Rarely")],
[tc("Cord coverings acquired"), tc("All three"), tc("External spermatic fascia only")],
[tc("Strangulation risk"), tc("Higher (narrow deep ring)"), tc("Lower (wide neck)")],
[tc("Sex predominance"), tc("Male >> Female"), tc("Male >> Female")],
[tc("Hesselbach's triangle"), tc("Outside (lateral)"), tc("Inside (medial)")],
]
story.append(table(compare_data, [4.5*cm, 4.5*cm, 5.0*cm]))
story.append(PageBreak())
# ─────────────────────────────────────────────────────────────────────────────
# SECTION 8 – HESSELBACH'S TRIANGLE
# ─────────────────────────────────────────────────────────────────────────────
story.append(SectionHeader("8. HESSELBACH'S TRIANGLE (Inguinal Triangle)"))
story.append(spacer(8))
story.append(body(
"Hesselbach's triangle (also called the <b>inguinal triangle</b>) is the region of potential "
"weakness through which direct inguinal hernias protrude. It lies on the posterior aspect of the "
"anterior abdominal wall and is best seen from within the abdomen (e.g. during laparoscopy)."
))
story.append(spacer(6))
hess_data = [
[tc("BOUNDARY", bold=True, center=True), tc("STRUCTURE", bold=True, center=True)],
[tc("Lateral (superolateral)"), tc("Inferior epigastric artery and vein")],
[tc("Medial"), tc("Lateral border of rectus abdominis muscle")],
[tc("Inferior (base)"), tc("Inguinal ligament")],
]
story.append(table(hess_data, [5*cm, 9*cm]))
story.append(spacer(6))
story.append(body(
"The floor of Hesselbach's triangle is the <b>transversalis fascia</b>, partially thickened "
"by the <b>iliopubic tract</b> (which runs parallel to and just posterior to the inguinal ligament). "
"The conjoint tendon reinforces the medial part of the triangle's posterior wall."
))
story.append(spacer(8))
for it in img("fig7_triangle.png", IW,
"Fig. 7 – Hesselbach's (inguinal) triangle: (A) Internal view showing the three boundaries. "
"(B) Laparoscopic view with parietal peritoneum intact. (Gray's Anatomy for Students)"):
story.append(it)
story.append(spacer(8))
hess_box = [
Paragraph("<b>Mnemonic for Hesselbach's triangle:</b> \"<b>RIL</b>\"", ParagraphStyle("hbx",
parent=S_callout, fontName="Helvetica-Bold", textColor=C_TEAL, fontSize=10)),
Paragraph("<b>R</b>ectus abdominis (medial) | <b>I</b>nferior epigastric vessels (lateral) | "
"<b>L</b>inguinal ligament (inferior)", S_callout),
]
story.append(ColorBox(hess_box, C_LIGHT_TEAL, C_TEAL))
story.append(PageBreak())
# ─────────────────────────────────────────────────────────────────────────────
# SECTION 9 – CREMASTERIC REFLEX
# ─────────────────────────────────────────────────────────────────────────────
story.append(SectionHeader("9. CREMASTERIC REFLEX"))
story.append(spacer(8))
story.append(body(
"The cremasteric reflex is a superficial (polysynaptic) reflex elicited by lightly stroking the "
"skin of the medial thigh, resulting in elevation of the ipsilateral testis."
))
story.append(spacer(6))
reflex_data = [
[tc("COMPONENT", bold=True, center=True), tc("DETAIL", bold=True, center=True)],
[tc("Afferent (sensory)"), tc("Ilioinguinal nerve (L1) — stroking medial thigh skin")],
[tc("Spinal level"), tc("L1 (also L2)")],
[tc("Efferent (motor)"), tc("Genital branch of genitofemoral nerve (L1/L2) — contracts cremaster muscle")],
[tc("Response"), tc("Elevation of the ipsilateral testis")],
[tc("Clinical significance"), tc("Absent in L1 cord lesion, upper motor neurone disease, testicular torsion, orchitis")],
[tc("Age notes"), tc("Brisk in infants and children; diminishes with age")],
]
story.append(table(reflex_data, [4.5*cm, 10*cm]))
story.append(spacer(8))
story.append(body(
"<b>Testicular torsion pearl:</b> Absence of the cremasteric reflex on the affected side is a "
"highly sensitive finding for testicular torsion and mandates urgent scrotal exploration."
))
story.append(PageBreak())
# ─────────────────────────────────────────────────────────────────────────────
# SECTION 10 – EMBRYOLOGY
# ─────────────────────────────────────────────────────────────────────────────
story.append(SectionHeader("10. EMBRYOLOGY & PROCESSUS VAGINALIS"))
story.append(spacer(8))
story.append(body(
"The inguinal canal forms during fetal development as a result of testicular descent from the "
"posterior abdominal wall into the scrotum:"
))
story.append(spacer(6))
embryo_steps = [
("<b>Week 7:</b>", "Testis lies on posterior abdominal wall; the <b>gubernaculum</b> — a ligamentous "
"mesenchymal condensation — connects the caudal pole of the testis to the developing scrotum"),
("<b>Months 2–7:</b>", "Testis descends retroperitoneally toward the inguinal canal, guided by the "
"gubernaculum"),
("<b>Month 7:</b>", "Testis enters the deep inguinal ring, pulling with it a sleeve of peritoneum "
"called the <b>processus vaginalis</b>"),
("<b>Month 8–9:</b>", "Testis traverses the canal, exits the superficial ring, and descends into "
"the scrotum"),
("<b>After birth:</b>", "The processus vaginalis normally <b>obliterates</b>, leaving only the "
"<b>tunica vaginalis</b> around the testis; failure to obliterate results in indirect inguinal "
"hernia or hydrocele"),
]
for timing, event in embryo_steps:
story.append(body(f"{timing} {event}"))
story.append(spacer(3))
story.append(spacer(6))
emb_data = [
[tc("PERSISTENT PROCESSUS VAGINALIS", bold=True, center=True),
tc("EXTENT OF PERSISTENCE", bold=True, center=True),
tc("RESULT", bold=True, center=True)],
[tc("Complete"), tc("Full length patent"), tc("Indirect inguinal hernia ± communicating hydrocele")],
[tc("Upper part only"), tc("Obliterated distally"), tc("Encysted hydrocele of the cord")],
[tc("Lower part only"), tc("Obliterated proximally"), tc("Hydrocele of the testis")],
[tc("Multiple gaps"), tc("Beaded"), tc("Hydrocele of canal of Nuck (females)")],
]
story.append(table(emb_data, [4.0*cm, 4.5*cm, 5.5*cm]))
story.append(spacer(6))
story.append(body(
"<b>In females:</b> the gubernaculum becomes the <b>ovarian ligament</b> (from ovary to uterus) "
"and the <b>round ligament of the uterus</b> (from uterus to labia majora). The round ligament "
"passes through the inguinal canal, analogous to the spermatic cord in males. Indirect inguinal "
"hernias do occur in females but are less common because the processus vaginalis (canal of Nuck) "
"usually obliterates completely."
))
story.append(PageBreak())
# ─────────────────────────────────────────────────────────────────────────────
# SECTION 11 – CLINICAL SUMMARY & MNEMONICS
# ─────────────────────────────────────────────────────────────────────────────
story.append(SectionHeader("11. CLINICAL SUMMARY & MNEMONICS"))
story.append(spacer(8))
story.append(h2("High-Yield Summary"))
hy_data = [
[tc("TOPIC", bold=True, center=True), tc("HIGH-YIELD POINT", bold=True, center=True)],
[tc("Deep ring location"), tc("Midpoint of ASIS–pubic symphysis line; just lateral to inf. epigastric vessels")],
[tc("Superficial ring location"), tc("Above and medial to pubic tubercle")],
[tc("Canal length"), tc("4–6 cm")],
[tc("Walls mnemonic"), tc("\"MAT Floor\": Muscles/Aponeurosis (ant.) | Transversalis fascia (post.) | Inguinal ligament (floor)")],
[tc("Cord coverings"), tc("\"Every Cremaster Is Important\": External → Cremasteric → Internal spermatic fascia")],
[tc("Indirect hernia"), tc("Lateral to inf. epigastric vessels; congenital; enters deep ring; can reach scrotum")],
[tc("Direct hernia"), tc("Medial to inf. epigastric vessels; acquired; through posterior wall (Hesselbach's)")],
[tc("Hesselbach's triangle"), tc("RIL: Rectus (medial) | Inferior epigastric (lateral) | Inguinal ligament (inferior)")],
[tc("Ilioinguinal nerve"), tc("L1; exits superficial ring; sensory to anterior scrotum/labia majora")],
[tc("Genitofemoral nerve (genital branch)"), tc("L1/L2; motor to cremaster; mediates cremasteric reflex")],
[tc("Testicular lymphatics"), tc("Para-aortic nodes at L1–L2 (NOT inguinal nodes)")],
[tc("Cremasteric reflex absent"), tc("Consider: L1 lesion, UMN disease, testicular torsion (urgent)")],
[tc("Processus vaginalis"), tc("Patent = indirect hernia/hydrocele; obliterated = normal outcome")],
]
story.append(table(hy_data, [5.5*cm, 9.0*cm]))
story.append(spacer(8))
story.append(h2("All Mnemonics at a Glance"))
mnemonics = [
("Walls", "\"MAT Floor\"",
"Muscles (int. oblique, ant. reinforcement) · Aponeurosis ext. oblique (ant.) · "
"Transversalis fascia (post.) · Floor = Inguinal lig."),
("Cord coverings", "\"Every Cremaster Is Important\"",
"External spermatic fascia · Cremasteric fascia · Internal spermatic fascia"),
("Hesselbach's triangle", "\"RIL\"",
"Rectus abdominis (medial) · Inferior epigastric vessels (lateral) · Inguinal ligament (base)"),
("Indirect vs. Direct", "\"I is Lateral, D is Medial\"",
"Indirect: lateral to inf. epigastric · Direct: medial to inf. epigastric"),
]
for topic, mem, detail in mnemonics:
mbox = [
Paragraph(f"<b>{topic}</b>", ParagraphStyle("mbh", parent=S_callout,
fontName="Helvetica-Bold", textColor=C_NAVY, fontSize=9)),
Paragraph(f"<font color='#{C_AMBER.hexval()[2:]}' size=11><b>{mem}</b></font>",
ParagraphStyle("mbm", parent=S_callout, fontSize=11, alignment=TA_CENTER)),
Paragraph(detail, S_callout),
]
story.append(ColorBox(mbox, C_AMBER_BG, C_AMBER))
story.append(spacer(6))
story.append(spacer(6))
story.append(rule(C_GRAY_LINE))
story.append(Paragraph(
"Sources: Gray's Anatomy for Students (2023) · Bailey & Love's Short Practice of Surgery, 28th ed. · "
"THIEME Atlas of General Anatomy & Musculoskeletal System · Sabiston Textbook of Surgery · "
"Campbell-Walsh-Wein Urology",
make_style("src", fontSize=7.5, textColor=C_CAPTION, alignment=TA_CENTER,
fontName="Helvetica-Oblique")
))
# ── Build ──────────────────────────────────────────────────────────────────
doc = SimpleDocTemplate(
OUT_PATH,
pagesize=A4,
leftMargin=MARGIN, rightMargin=MARGIN,
topMargin=2.2*cm, bottomMargin=2.2*cm,
title="Inguinal Canal – Anatomy Reference",
author="Orris Medical",
subject="Human Anatomy",
)
doc.build(story, canvasmaker=HeaderFooterCanvas)
print(f"PDF saved: {OUT_PATH}")
cd /home/daytona/workspace/inguinal-canal-pdf && python generate_pdf.py 2>&1
~/inguinal-canal-pdf/generate_pdf.py
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/inguinal-canal-pdf/generate_pdf.py'
~/inguinal-canal-pdf/generate_pdf.py
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/inguinal-canal-pdf/generate_pdf.py'
cd /home/daytona/workspace/inguinal-canal-pdf && python generate_pdf.py 2>&1
~/inguinal-canal-pdf/generate_pdf.py
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/inguinal-canal-pdf/generate_pdf.py'
~/inguinal-canal-pdf/generate_pdf.py
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/inguinal-canal-pdf/generate_pdf.py'
~/inguinal-canal-pdf/generate_pdf.py
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/inguinal-canal-pdf/generate_pdf.py'
cd /home/daytona/workspace/inguinal-canal-pdf && python generate_pdf.py 2>&1
ls -lh /home/daytona/workspace/inguinal-canal-pdf/Inguinal_Canal_Anatomy_Reference.pdf
Inguinal Canal Anatomy Reference
PDF Document · PDF
| Section | Content |
|---|---|
| Cover page | Styled title page with design and source credits |
| Table of contents | Numbered, linked to all 11 sections |
| 1. Overview | Definition, dimensions, orientation, key-facts callout box |
| 2. Openings | Deep ring (location, structure, muscular guard) + superficial ring (crura, intercrural fibres) - with 2 labelled diagrams |
| 3. Walls | All four walls in a comparison table + mnemonic callout box + sagittal cross-section image |
| 4. Contents | Detailed table for male contents (structure/function/origin); female contents; lymphatic drainage clinical note |
| 5. Spermatic cord layers | Three-layer table with derivation + mnemonic "Every Cremaster Is Important" |
| 6. Neurovascular relations | Nerve table (ilioinguinal, genitofemoral, iliohypogastric) + inferior epigastric vessel landmarks |
| 7. Inguinal hernias | Indirect vs. direct with diagrams; full comparison table (8 features) |
| 8. Hesselbach's triangle | Boundaries table + laparoscopic view image + "RIL" mnemonic |
| 9. Cremasteric reflex | Arc components table; torsion clinical pearl |
| 10. Embryology | Step-by-step testicular descent; processus vaginalis outcomes table |
| 11. Clinical summary | High-yield 14-point table; all mnemonics in coloured callout boxes |