Suggest me a video to study thyroid gland
best YouTube video thyroid gland anatomy physiology study
Prepare a 10 question quiz on muscles of mastication
Create a printable PDF quiz with answer key
ls /home/daytona/workspace/ 2>/dev/null || echo "workspace empty or missing"
mkdir -p /home/daytona/workspace/mastication-quiz
~/mastication-quiz/generate_quiz_pdf.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import mm
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, HRFlowable,
Table, TableStyle, PageBreak, KeepTogether
)
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.pdfgen import canvas
from reportlab.platypus.flowables import Flowable
# ── Colour palette ──────────────────────────────────────────────────────────
DARK_BLUE = colors.HexColor("#1A3A5C")
MID_BLUE = colors.HexColor("#2E6DA4")
LIGHT_BLUE = colors.HexColor("#D6E8F7")
ACCENT = colors.HexColor("#E07B39")
ANSWER_BG = colors.HexColor("#EAF4EA")
ANSWER_LINE = colors.HexColor("#3A8A3A")
LIGHT_GRAY = colors.HexColor("#F5F5F5")
RULE_GRAY = colors.HexColor("#CCCCCC")
WHITE = colors.white
PAGE_W, PAGE_H = A4
MARGIN = 20 * mm
# ── Quiz data ────────────────────────────────────────────────────────────────
QUESTIONS = [
{
"n": 1,
"q": "Which four muscles are classically considered the primary muscles of mastication?",
"a": "Masseter, Temporalis, Medial pterygoid, and Lateral pterygoid. All are innervated by the mandibular nerve (V3).",
"ref": "Gray's Anatomy for Students, Table 8.11"
},
{
"n": 2,
"q": "The masseter originates from the zygomatic arch. Where does it insert, and what is its primary function?",
"a": "Inserts on the lateral surface of the ramus of the mandible. Primary function: elevation (and protrusion) of the mandible.",
"ref": "Gray's Anatomy for Students, Table 8.11"
},
{
"n": 3,
"q": "The temporalis can both elevate AND retract the mandible. What is its insertion, and which part of the muscle is responsible for retraction?",
"a": "Inserts on the coronoid process of the mandible and the anterior margin of the ramus. The posterior fibres are responsible for retraction; anterior/middle fibres elevate.",
"ref": "Cummings Otolaryngology, Table 92.1"
},
{
"n": 4,
"q": "All four primary muscles of mastication share the same nerve supply. Name the nerve and its parent cranial nerve trunk.",
"a": "All are innervated by the mandibular nerve (V3), the third division of the trigeminal nerve (CN V). Specific branches: masseteric nerve, deep temporal nerves, and nerves to the medial and lateral pterygoids.",
"ref": "Gray's Anatomy for Students, Table 8.11"
},
{
"n": 5,
"q": "The lateral pterygoid has two heads with different functions. Describe the action of each head.",
"a": "Superior head: elevation and protrusion of the mandible. Inferior head: depression and lateral displacement of the mandible. The inferior head is the main driver of jaw opening.",
"ref": "Cummings Otolaryngology, Ch. 86"
},
{
"n": 6,
"q": "Which muscle inserts on the medial surface of the angle of the mandible, and what movements does it produce?",
"a": "The medial pterygoid. It produces elevation, protrusion, and lateral (side-to-side) movements of the mandible. Together with the masseter it forms a muscular sling around the mandibular angle.",
"ref": "Cummings Otolaryngology, Table 92.1"
},
{
"n": 7,
"q": "Which three muscles are responsible for depression (opening) of the mandible, and what nerve innervates the mylohyoid?",
"a": "The anterior belly of the digastric, mylohyoid, and geniohyoid. The mylohyoid is innervated by the nerve to mylohyoid, a branch of the inferior alveolar nerve (V3).",
"ref": "Cummings Otolaryngology, Table 92.1"
},
{
"n": 8,
"q": "A patient presents with inability to protrude the jaw and difficulty with side-to-side chewing. Damage to which muscle is most likely responsible? What sign appears on jaw opening if only one side is affected?",
"a": "The lateral pterygoid. Bilateral damage prevents protrusion. Unilateral damage causes the jaw to deviate toward the side of the lesion on opening.",
"ref": "Cummings Otolaryngology, Ch. 86"
},
{
"n": 9,
"q": "What is the blood supply to all four primary muscles of mastication?",
"a": "Branches of the maxillary artery (a terminal branch of the external carotid artery).",
"ref": "K.J. Lee's Essential Otolaryngology"
},
{
"n": 10,
"q": "During jaw opening, the mandible does not simply rotate — what additional movement occurs at the TMJ, and which muscle drives it?",
"a": "The mandibular head translates (protrudes) forward onto the articular tubercle in addition to rotating. This forward translation is driven primarily by the lateral pterygoid (inferior head), assisted by gravity and the suprahyoid muscles.",
"ref": "Gray's Anatomy for Students, p. 1116"
},
]
# ── Page numbering canvas ────────────────────────────────────────────────────
class NumberedCanvas(canvas.Canvas):
def __init__(self, *args, **kwargs):
canvas.Canvas.__init__(self, *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_page_number(num_pages)
canvas.Canvas.showPage(self)
canvas.Canvas.save(self)
def draw_page_number(self, page_count):
pg = self._pageNumber
self.setFont("Helvetica", 8)
self.setFillColor(colors.HexColor("#888888"))
self.drawRightString(PAGE_W - MARGIN, 10 * mm,
f"Page {pg} of {page_count}")
self.drawString(MARGIN, 10 * mm, "Muscles of Mastication Quiz")
# ── Header / footer watermark on every page ──────────────────────────────────
def header_footer(canvas_obj, doc):
canvas_obj.saveState()
# Top colour bar
canvas_obj.setFillColor(DARK_BLUE)
canvas_obj.rect(0, PAGE_H - 14 * mm, PAGE_W, 14 * mm, fill=1, stroke=0)
canvas_obj.setFillColor(WHITE)
canvas_obj.setFont("Helvetica-Bold", 11)
canvas_obj.drawString(MARGIN, PAGE_H - 9 * mm, "Muscles of Mastication")
canvas_obj.setFont("Helvetica", 9)
canvas_obj.drawRightString(PAGE_W - MARGIN, PAGE_H - 9 * mm,
"Anatomy Quiz")
# Bottom rule
canvas_obj.setStrokeColor(RULE_GRAY)
canvas_obj.setLineWidth(0.5)
canvas_obj.line(MARGIN, 15 * mm, PAGE_W - MARGIN, 15 * mm)
canvas_obj.restoreState()
# ── Build PDF ────────────────────────────────────────────────────────────────
def build_pdf(output_path):
doc = SimpleDocTemplate(
output_path,
pagesize=A4,
leftMargin=MARGIN,
rightMargin=MARGIN,
topMargin=22 * mm,
bottomMargin=22 * mm,
title="Muscles of Mastication – Quiz & Answer Key",
author="Orris Medical Education",
)
styles = getSampleStyleSheet()
# Custom styles
title_style = ParagraphStyle(
"QuizTitle",
parent=styles["Title"],
fontSize=22,
textColor=DARK_BLUE,
spaceAfter=4,
alignment=TA_CENTER,
fontName="Helvetica-Bold",
)
subtitle_style = ParagraphStyle(
"Subtitle",
parent=styles["Normal"],
fontSize=11,
textColor=MID_BLUE,
spaceAfter=2,
alignment=TA_CENTER,
fontName="Helvetica",
)
instr_style = ParagraphStyle(
"Instructions",
parent=styles["Normal"],
fontSize=9,
textColor=colors.HexColor("#555555"),
spaceAfter=2,
alignment=TA_CENTER,
fontName="Helvetica-Oblique",
)
q_label_style = ParagraphStyle(
"QLabel",
parent=styles["Normal"],
fontSize=10,
textColor=WHITE,
fontName="Helvetica-Bold",
leading=14,
)
q_text_style = ParagraphStyle(
"QText",
parent=styles["Normal"],
fontSize=11,
textColor=DARK_BLUE,
fontName="Helvetica-Bold",
leading=15,
spaceAfter=3,
)
blank_label_style = ParagraphStyle(
"BlankLabel",
parent=styles["Normal"],
fontSize=9,
textColor=colors.HexColor("#777777"),
fontName="Helvetica-Oblique",
)
section_style = ParagraphStyle(
"SectionHead",
parent=styles["Normal"],
fontSize=15,
textColor=WHITE,
fontName="Helvetica-Bold",
leading=20,
alignment=TA_CENTER,
)
ans_q_style = ParagraphStyle(
"AnsQ",
parent=styles["Normal"],
fontSize=10,
textColor=DARK_BLUE,
fontName="Helvetica-Bold",
leading=14,
spaceAfter=2,
)
ans_text_style = ParagraphStyle(
"AnsText",
parent=styles["Normal"],
fontSize=10,
textColor=colors.HexColor("#1A5C1A"),
fontName="Helvetica",
leading=14,
spaceAfter=2,
)
ref_style = ParagraphStyle(
"Ref",
parent=styles["Normal"],
fontSize=8,
textColor=colors.HexColor("#888888"),
fontName="Helvetica-Oblique",
spaceAfter=0,
)
score_style = ParagraphStyle(
"Score",
parent=styles["Normal"],
fontSize=10,
textColor=DARK_BLUE,
fontName="Helvetica",
leading=15,
)
story = []
# ── COVER / TITLE ──────────────────────────────────────────────────────
story.append(Spacer(1, 10 * mm))
story.append(Paragraph("Muscles of Mastication", title_style))
story.append(Paragraph("10-Question Anatomy Quiz", subtitle_style))
story.append(Spacer(1, 3 * mm))
story.append(HRFlowable(width="60%", thickness=2, color=ACCENT,
hAlign="CENTER"))
story.append(Spacer(1, 4 * mm))
story.append(Paragraph(
"Name: ______________________________________ "
"Date: _________________ "
"Score: _______ / 10",
ParagraphStyle("NameLine", parent=styles["Normal"], fontSize=10,
textColor=DARK_BLUE, fontName="Helvetica",
alignment=TA_CENTER)
))
story.append(Spacer(1, 3 * mm))
story.append(Paragraph(
"Write your answers in the space provided. Each question carries 1 mark.",
instr_style
))
story.append(Spacer(1, 6 * mm))
# ── QUESTIONS ──────────────────────────────────────────────────────────
for item in QUESTIONS:
# Question number badge + text in a table
badge = Table(
[[Paragraph(f"Q{item['n']}", q_label_style),
Paragraph(item["q"], q_text_style)]],
colWidths=[10 * mm, PAGE_W - 2 * MARGIN - 10 * mm],
)
badge.setStyle(TableStyle([
("BACKGROUND", (0, 0), (0, 0), ACCENT),
("BACKGROUND", (1, 0), (1, 0), LIGHT_BLUE),
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
("LEFTPADDING", (0, 0), (0, 0), 4),
("RIGHTPADDING", (0, 0), (0, 0), 4),
("LEFTPADDING", (1, 0), (1, 0), 6),
("TOPPADDING", (0, 0), (-1, -1), 5),
("BOTTOMPADDING", (0, 0), (-1, -1), 5),
("ROUNDEDCORNERS", [3, 3, 3, 3]),
]))
# Answer lines
lines_table = Table(
[[""] for _ in range(3)],
colWidths=[PAGE_W - 2 * MARGIN],
rowHeights=[8 * mm] * 3,
)
lines_table.setStyle(TableStyle([
("LINEBELOW", (0, 0), (-1, -1), 0.5, RULE_GRAY),
("BACKGROUND", (0, 0), (-1, -1), LIGHT_GRAY),
("LEFTPADDING", (0, 0), (-1, -1), 4),
]))
block = KeepTogether([
badge,
Spacer(1, 2 * mm),
lines_table,
Spacer(1, 5 * mm),
])
story.append(block)
# ── SCORE GUIDE ────────────────────────────────────────────────────────
story.append(Spacer(1, 2 * mm))
score_data = [
[Paragraph("<b>Score Guide</b>", ParagraphStyle(
"SG", parent=styles["Normal"], fontSize=10,
textColor=DARK_BLUE, fontName="Helvetica-Bold"))],
[Paragraph(
"9–10 Excellent | "
"7–8 Good | "
"5–6 Review pterygoids & functions | "
"<5 Re-read the chapter",
score_style)]
]
score_table = Table(score_data, colWidths=[PAGE_W - 2 * MARGIN])
score_table.setStyle(TableStyle([
("BACKGROUND", (0, 0), (-1, -1), LIGHT_BLUE),
("BOX", (0, 0), (-1, -1), 1, MID_BLUE),
("LEFTPADDING", (0, 0), (-1, -1), 8),
("TOPPADDING", (0, 0), (-1, -1), 5),
("BOTTOMPADDING", (0, 0), (-1, -1), 5),
]))
story.append(score_table)
# ── PAGE BREAK → ANSWER KEY ────────────────────────────────────────────
story.append(PageBreak())
# Answer key header band
ak_header = Table(
[[Paragraph("ANSWER KEY", section_style)]],
colWidths=[PAGE_W - 2 * MARGIN],
rowHeights=[14 * mm],
)
ak_header.setStyle(TableStyle([
("BACKGROUND", (0, 0), (-1, -1), DARK_BLUE),
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
("LEFTPADDING", (0, 0), (-1, -1), 0),
]))
story.append(ak_header)
story.append(Spacer(1, 4 * mm))
story.append(Paragraph(
"Muscles of Mastication – Detailed Answers & References",
ParagraphStyle("AKSub", parent=styles["Normal"], fontSize=11,
textColor=MID_BLUE, fontName="Helvetica-Oblique",
alignment=TA_CENTER)
))
story.append(Spacer(1, 5 * mm))
for item in QUESTIONS:
# Question row
q_row = Table(
[[Paragraph(f"Q{item['n']}", q_label_style),
Paragraph(item["q"], ans_q_style)]],
colWidths=[10 * mm, PAGE_W - 2 * MARGIN - 10 * mm],
)
q_row.setStyle(TableStyle([
("BACKGROUND", (0, 0), (0, 0), ACCENT),
("BACKGROUND", (1, 0), (1, 0), LIGHT_BLUE),
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
("LEFTPADDING", (0, 0), (0, 0), 4),
("LEFTPADDING", (1, 0), (1, 0), 6),
("TOPPADDING", (0, 0), (-1, -1), 4),
("BOTTOMPADDING", (0, 0), (-1, -1), 4),
]))
# Answer box
ans_box = Table(
[[Paragraph(f"<b>Answer:</b> {item['a']}", ans_text_style)],
[Paragraph(f"Reference: {item['ref']}", ref_style)]],
colWidths=[PAGE_W - 2 * MARGIN],
)
ans_box.setStyle(TableStyle([
("BACKGROUND", (0, 0), (-1, -1), ANSWER_BG),
("LINEAFTER", (0, 0), (0, -1), 3, ANSWER_LINE),
("LEFTPADDING", (0, 0), (-1, -1), 8),
("TOPPADDING", (0, 0), (-1, -1), 4),
("BOTTOMPADDING", (0, 0), (-1, -1), 4),
("BOX", (0, 0), (-1, -1), 0.5, RULE_GRAY),
]))
block = KeepTogether([
q_row,
ans_box,
Spacer(1, 5 * mm),
])
story.append(block)
# ── FOOTER NOTE ────────────────────────────────────────────────────────
story.append(HRFlowable(width="100%", thickness=0.5, color=RULE_GRAY))
story.append(Spacer(1, 2 * mm))
story.append(Paragraph(
"Sources: Gray's Anatomy for Students (Table 8.11) · "
"Cummings Otolaryngology Head & Neck Surgery (Tables 86.2, 92.1) · "
"K.J. Lee's Essential Otolaryngology",
ParagraphStyle("FootNote", parent=styles["Normal"], fontSize=7.5,
textColor=colors.HexColor("#888888"),
fontName="Helvetica-Oblique", alignment=TA_CENTER)
))
doc.build(story, onFirstPage=header_footer, onLaterPages=header_footer,
canvasmaker=NumberedCanvas)
print("PDF created successfully.")
if __name__ == "__main__":
build_pdf("/home/daytona/workspace/mastication-quiz/Muscles_of_Mastication_Quiz.pdf")
python3 /home/daytona/workspace/mastication-quiz/generate_quiz_pdf.py
ls -lh /home/daytona/workspace/mastication-quiz/
Muscles of Mastication Quiz
PDF Document · PDF
Create a quiz of 10 questions on topic tmj
Create a visual diagram of TMJ anatomy and movements
Detailed anatomical educational diagram of the Temporomandibular Joint (TMJ) showing two panels side by side. Left panel: cross-sectional sagittal view of TMJ anatomy with clearly labeled structures including: mandibular fossa, articular eminence, articular disc (meniscus) with its anterior band, intermediate zone, and posterior band, upper joint compartment (gliding movement), lower joint compartment (hinge movement), mandibular condyle, pterygoid fovea, retrodiscal tissue (bilaminar zone) with superior elastic lamina and inferior non-elastic lamina, joint capsule, synovial membrane, lateral pterygoid muscle attachment, sphenomandibular ligament, lateral temporomandibular ligament, and stylomandibular ligament. Right panel: diagram of TMJ movements of the mandible showing four movement types with arrows: Elevation (jaw closing - upward arrow), Depression (jaw opening - downward arrow with condyle translating forward), Protrusion (forward arrow), Retraction (backward arrow), and Lateral excursion (side-to-side arrow). Each movement labeled with the primary muscles responsible. Clean medical illustration style on white background, blue and orange color scheme, professional anatomy textbook quality with all labels in clear sans-serif font.

Prepare a 15 questions quiz on dural venous sinuses
Create a printable PDF quiz with answer key on dural venous sinuses
~/dvs-quiz/generate_dvs_quiz.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import mm
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, HRFlowable,
Table, TableStyle, PageBreak, KeepTogether
)
from reportlab.lib.enums import TA_CENTER, TA_LEFT
from reportlab.pdfgen import canvas
# ── Colour palette ───────────────────────────────────────────────────────────
DARK_TEAL = colors.HexColor("#0D4A5A")
MID_TEAL = colors.HexColor("#1A7A96")
LIGHT_TEAL = colors.HexColor("#D0EEF5")
ACCENT = colors.HexColor("#C0582A")
ANSWER_BG = colors.HexColor("#EAF4F0")
ANSWER_LINE = colors.HexColor("#1A8A5A")
LIGHT_GRAY = colors.HexColor("#F7F7F7")
RULE_GRAY = colors.HexColor("#CCCCCC")
WHITE = colors.white
PAGE_W, PAGE_H = A4
MARGIN = 20 * mm
# ── Quiz data ────────────────────────────────────────────────────────────────
QUESTIONS = [
{
"n": 1,
"q": "What are dural venous sinuses, and what is unique about their walls compared to peripheral veins?",
"a": (
"Dural venous sinuses are venous channels of the dura mater that drain blood from the brain into "
"the internal jugular veins. Their rigid walls are formed by cranial periosteum and dura mater "
"(not a muscular wall like peripheral veins). Their interior is lined with endothelium, and they "
"are valveless."
),
"ref": "Color Atlas of Human Anatomy Vol. 2, p. 134"
},
{
"n": 2,
"q": "Name at least 10 dural venous sinuses.",
"a": (
"Superior sagittal, Inferior sagittal, Straight, Occipital, Confluence of sinuses (torcular Herophili), "
"Transverse (R & L), Sigmoid (R & L), Cavernous (R & L), Intercavernous, Sphenoparietal (R & L), "
"Superior petrosal (R & L), Inferior petrosal (R & L), Basilar plexus, Marginal sinus."
),
"ref": "Gray's Anatomy for Students, Table 8.3"
},
{
"n": 3,
"q": "Where is the superior sagittal sinus located, and what does it receive?",
"a": (
"It runs along the superior border of the falx cerebri, ending at the confluence of sinuses. "
"It receives superior cerebral veins, diploic veins, emissary veins, and CSF via arachnoid granulations."
),
"ref": "Gray's Anatomy for Students, Table 8.3"
},
{
"n": 4,
"q": "The straight sinus lies at the junction of two dural folds. Name them, and list its tributaries.",
"a": (
"Junction of the falx cerebri and tentorium cerebelli. Receives: inferior sagittal sinus, great "
"cerebral vein (of Galen), posterior cerebral veins, superior cerebellar veins, and veins from "
"the falx cerebri."
),
"ref": "Gray's Anatomy for Students, Table 8.3"
},
{
"n": 5,
"q": "What is the confluence of sinuses (torcular Herophili)? Where is it located, and which sinuses meet there?",
"a": (
"A dilated venous space at the internal occipital protuberance. Receives: superior sagittal, straight, "
"and occipital sinuses. Gives rise to the right and left transverse sinuses."
),
"ref": "Gray's Anatomy for Students, Table 8.3; Color Atlas of Human Anatomy, p. 134"
},
{
"n": 6,
"q": "Describe the course of the sigmoid sinus from its origin to its termination.",
"a": (
"The sigmoid sinus is a continuation of the transverse sinus. It travels in an S-shaped course "
"along the posterior inferior border of the petrous temporal bone (grooving the parietal, temporal, "
"and occipital bones) and terminates at the jugular foramen, where it becomes the internal jugular vein."
),
"ref": "Color Atlas of Human Anatomy Vol. 2, p. 134"
},
{
"n": 7,
"q": (
"List all structures that pass through the cavernous sinus itself, and all structures "
"that run within its lateral wall."
),
"a": (
"THROUGH the sinus: Internal carotid artery; Abducent nerve (CN VI).\n"
"LATERAL WALL (medial to lateral): Oculomotor nerve (CN III); Trochlear nerve (CN IV); "
"Ophthalmic nerve (V1); Maxillary nerve (V2)."
),
"ref": "Color Atlas of Human Anatomy, p. 134; Harrison's Principles of Internal Medicine 22E"
},
{
"n": 8,
"q": "Name four tributaries that drain into the cavernous sinus.",
"a": (
"1. Superior and inferior ophthalmic veins (connection to facial/angular vein)\n"
"2. Sphenoparietal sinus\n"
"3. Superficial cerebral veins\n"
"4. Emissary veins from the pterygoid plexus\n"
"(Intercavernous sinuses connect the two cavernous sinuses to each other)"
),
"ref": "Gray's Anatomy for Students, Table 8.3"
},
{
"n": 9,
"q": (
"What is the clinical significance of the connection between the cavernous sinus and the facial vein? "
"Give a clinical example."
),
"a": (
"The cavernous sinus communicates with the facial/angular vein via the superior ophthalmic vein "
"through a valveless pathway. Infection from the 'danger triangle of the face' (nose, upper lip) "
"can spread retrogradely to cause septic cavernous sinus thrombosis - a life-threatening condition."
),
"ref": "Color Atlas of Human Anatomy, p. 134"
},
{
"n": 10,
"q": (
"A patient develops fever, headache, proptosis, chemosis, and painful ophthalmoplegia with ptosis. "
"What is the diagnosis and which cranial nerves are involved?"
),
"a": (
"Septic cavernous sinus thrombosis. Cranial nerves affected:\n"
"CN III (ptosis, ophthalmoplegia), CN IV (superior oblique palsy), "
"CN VI (lateral gaze palsy - often earliest), CN V1 & V2 (hyperesthesia, decreased corneal reflex). "
"May also show dilated tortuous retinal veins and papilledema."
),
"ref": "Harrison's Principles of Internal Medicine 22E"
},
{
"n": 11,
"q": (
"Thrombosis of the transverse sinus presents with a characteristic triad. "
"What is it, and what condition can cause it?"
),
"a": (
"Headache, otalgia (earache), and CN VI palsy with retroorbital/facial pain - known as Gradenigo's syndrome. "
"Strongly associated with otitis media (infection spreading from mastoid/middle ear to the "
"adjacent sigmoid-transverse sinus system)."
),
"ref": "Harrison's Principles of Internal Medicine 22E"
},
{
"n": 12,
"q": "What is the marginal sinus and what does it connect?",
"a": (
"The marginal sinus encircles the foramen magnum and connects the dural venous sinuses "
"with the vertebral venous plexuses of the spine, forming a link between the intracranial "
"and extracranial epidural venous systems."
),
"ref": "Color Atlas of Human Anatomy Vol. 2, p. 134"
},
{
"n": 13,
"q": (
"What is the preferred imaging modality for diagnosing dural venous sinus thrombosis, "
"and what is the key diagnostic finding on MRI?"
),
"a": (
"Contrast-enhanced MR venography (MRV) is the modality of choice. On standard MRI, the key finding "
"is an absent flow void within the affected sinus. CT venography or the venous phase of cerebral "
"angiography can also confirm the diagnosis."
),
"ref": "Harrison's Principles of Internal Medicine 22E"
},
{
"n": 14,
"q": (
"What is the first-line treatment for aseptic dural venous sinus thrombosis, and is the presence "
"of small hemorrhagic infarction an absolute contraindication to this treatment?"
),
"a": (
"First-line treatment is anticoagulation with unfractionated or low-molecular-weight heparin, "
"followed by warfarin or a newer anticoagulant. A small intracerebral hemorrhage from septic "
"thrombophlebitis is NOT an absolute contraindication to heparin - anticoagulation is still "
"recommended with progressive neurological deterioration despite antibiotics."
),
"ref": "Harrison's 22E; Adams & Victor's Principles of Neurology, 12th Ed."
},
{
"n": 15,
"q": (
"Deep cerebral vein thrombosis (occlusion of the vein of Galen and internal cerebral veins) "
"produces a distinctive clinical picture. Describe it and identify the key MRI finding."
),
"a": (
"Produces bithalamic infarction: inattention, spatial neglect, amnesia, akinetic mutism, and apathy. "
"Coma and pupillary changes may occur in severe cases. Key MRI finding: a large bilobular region of "
"signal change encompassing both thalami (often reversible edema). Confirmed by MR venography."
),
"ref": "Adams & Victor's Principles of Neurology, 12th Ed."
},
]
# ── Page numbering canvas ────────────────────────────────────────────────────
class NumberedCanvas(canvas.Canvas):
def __init__(self, *args, **kwargs):
canvas.Canvas.__init__(self, *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_page_number(num_pages)
canvas.Canvas.showPage(self)
canvas.Canvas.save(self)
def draw_page_number(self, page_count):
pg = self._pageNumber
self.setFont("Helvetica", 8)
self.setFillColor(colors.HexColor("#888888"))
self.drawRightString(PAGE_W - MARGIN, 10 * mm,
f"Page {pg} of {page_count}")
self.drawString(MARGIN, 10 * mm, "Dural Venous Sinuses Quiz")
# ── Per-page header/footer ────────────────────────────────────────────────────
def header_footer(canvas_obj, doc):
canvas_obj.saveState()
canvas_obj.setFillColor(DARK_TEAL)
canvas_obj.rect(0, PAGE_H - 14 * mm, PAGE_W, 14 * mm, fill=1, stroke=0)
canvas_obj.setFillColor(WHITE)
canvas_obj.setFont("Helvetica-Bold", 11)
canvas_obj.drawString(MARGIN, PAGE_H - 9 * mm, "Dural Venous Sinuses")
canvas_obj.setFont("Helvetica", 9)
canvas_obj.drawRightString(PAGE_W - MARGIN, PAGE_H - 9 * mm, "Neuroanatomy Quiz")
canvas_obj.setStrokeColor(RULE_GRAY)
canvas_obj.setLineWidth(0.5)
canvas_obj.line(MARGIN, 15 * mm, PAGE_W - MARGIN, 15 * mm)
canvas_obj.restoreState()
# ── Build PDF ─────────────────────────────────────────────────────────────────
def build_pdf(output_path):
doc = SimpleDocTemplate(
output_path,
pagesize=A4,
leftMargin=MARGIN,
rightMargin=MARGIN,
topMargin=22 * mm,
bottomMargin=22 * mm,
title="Dural Venous Sinuses – Quiz & Answer Key",
author="Orris Medical Education",
)
styles = getSampleStyleSheet()
title_style = ParagraphStyle(
"QuizTitle", parent=styles["Title"],
fontSize=22, textColor=DARK_TEAL,
spaceAfter=4, alignment=TA_CENTER, fontName="Helvetica-Bold",
)
subtitle_style = ParagraphStyle(
"Subtitle", parent=styles["Normal"],
fontSize=11, textColor=MID_TEAL,
spaceAfter=2, alignment=TA_CENTER, fontName="Helvetica",
)
instr_style = ParagraphStyle(
"Instructions", parent=styles["Normal"],
fontSize=9, textColor=colors.HexColor("#555555"),
spaceAfter=2, alignment=TA_CENTER, fontName="Helvetica-Oblique",
)
q_label_style = ParagraphStyle(
"QLabel", parent=styles["Normal"],
fontSize=10, textColor=WHITE,
fontName="Helvetica-Bold", leading=14,
)
q_text_style = ParagraphStyle(
"QText", parent=styles["Normal"],
fontSize=10.5, textColor=DARK_TEAL,
fontName="Helvetica-Bold", leading=14, spaceAfter=3,
)
section_style = ParagraphStyle(
"SectionHead", parent=styles["Normal"],
fontSize=15, textColor=WHITE,
fontName="Helvetica-Bold", leading=20, alignment=TA_CENTER,
)
ans_q_style = ParagraphStyle(
"AnsQ", parent=styles["Normal"],
fontSize=10, textColor=DARK_TEAL,
fontName="Helvetica-Bold", leading=13, spaceAfter=2,
)
ans_text_style = ParagraphStyle(
"AnsText", parent=styles["Normal"],
fontSize=9.5, textColor=colors.HexColor("#0A5C30"),
fontName="Helvetica", leading=13, spaceAfter=2,
)
ref_style = ParagraphStyle(
"Ref", parent=styles["Normal"],
fontSize=7.5, textColor=colors.HexColor("#888888"),
fontName="Helvetica-Oblique", spaceAfter=0,
)
score_style = ParagraphStyle(
"Score", parent=styles["Normal"],
fontSize=10, textColor=DARK_TEAL,
fontName="Helvetica", leading=15,
)
story = []
# ── TITLE ──────────────────────────────────────────────────────────────
story.append(Spacer(1, 8 * mm))
story.append(Paragraph("Dural Venous Sinuses", title_style))
story.append(Paragraph("15-Question Neuroanatomy Quiz", subtitle_style))
story.append(Spacer(1, 3 * mm))
story.append(HRFlowable(width="60%", thickness=2, color=ACCENT, hAlign="CENTER"))
story.append(Spacer(1, 4 * mm))
story.append(Paragraph(
"Name: ______________________________________ "
"Date: _________________ "
"Score: _______ / 15",
ParagraphStyle("NameLine", parent=styles["Normal"], fontSize=10,
textColor=DARK_TEAL, fontName="Helvetica",
alignment=TA_CENTER)
))
story.append(Spacer(1, 3 * mm))
story.append(Paragraph(
"Write your answers in the space provided. Each question carries 1 mark.",
instr_style
))
story.append(Spacer(1, 5 * mm))
# ── QUESTIONS ──────────────────────────────────────────────────────────
for item in QUESTIONS:
badge = Table(
[[Paragraph(f"Q{item['n']}", q_label_style),
Paragraph(item["q"], q_text_style)]],
colWidths=[10 * mm, PAGE_W - 2 * MARGIN - 10 * mm],
)
badge.setStyle(TableStyle([
("BACKGROUND", (0, 0), (0, 0), ACCENT),
("BACKGROUND", (1, 0), (1, 0), LIGHT_TEAL),
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
("LEFTPADDING", (0, 0), (0, 0), 4),
("RIGHTPADDING", (0, 0), (0, 0), 4),
("LEFTPADDING", (1, 0), (1, 0), 6),
("TOPPADDING", (0, 0), (-1, -1), 5),
("BOTTOMPADDING",(0, 0), (-1, -1), 5),
]))
# For Q2 and Q7/Q8 (listing questions) give 5 lines; others get 3
n_lines = 5 if item["n"] in (2, 7, 8, 10) else 3
lines_table = Table(
[[""] for _ in range(n_lines)],
colWidths=[PAGE_W - 2 * MARGIN],
rowHeights=[7.5 * mm] * n_lines,
)
lines_table.setStyle(TableStyle([
("LINEBELOW", (0, 0), (-1, -1), 0.5, RULE_GRAY),
("BACKGROUND", (0, 0), (-1, -1), LIGHT_GRAY),
("LEFTPADDING", (0, 0), (-1, -1), 4),
]))
block = KeepTogether([
badge,
Spacer(1, 2 * mm),
lines_table,
Spacer(1, 4 * mm),
])
story.append(block)
# ── SCORE GUIDE ────────────────────────────────────────────────────────
story.append(Spacer(1, 1 * mm))
score_data = [
[Paragraph("<b>Score Guide</b>", ParagraphStyle(
"SG", parent=styles["Normal"], fontSize=10,
textColor=DARK_TEAL, fontName="Helvetica-Bold"))],
[Paragraph(
"14–15 Outstanding | 11–13 Strong | 8–10 Review tributaries & clinical syndromes | <8 Re-read the chapter",
score_style)]
]
score_table = Table(score_data, colWidths=[PAGE_W - 2 * MARGIN])
score_table.setStyle(TableStyle([
("BACKGROUND", (0, 0), (-1, -1), LIGHT_TEAL),
("BOX", (0, 0), (-1, -1), 1, MID_TEAL),
("LEFTPADDING", (0, 0), (-1, -1), 8),
("TOPPADDING", (0, 0), (-1, -1), 5),
("BOTTOMPADDING",(0, 0), (-1, -1), 5),
]))
story.append(score_table)
# ── PAGE BREAK → ANSWER KEY ────────────────────────────────────────────
story.append(PageBreak())
ak_header = Table(
[[Paragraph("ANSWER KEY", section_style)]],
colWidths=[PAGE_W - 2 * MARGIN],
rowHeights=[14 * mm],
)
ak_header.setStyle(TableStyle([
("BACKGROUND", (0, 0), (-1, -1), DARK_TEAL),
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
]))
story.append(ak_header)
story.append(Spacer(1, 4 * mm))
story.append(Paragraph(
"Dural Venous Sinuses – Detailed Answers & References",
ParagraphStyle("AKSub", parent=styles["Normal"], fontSize=11,
textColor=MID_TEAL, fontName="Helvetica-Oblique",
alignment=TA_CENTER)
))
story.append(Spacer(1, 5 * mm))
for item in QUESTIONS:
q_row = Table(
[[Paragraph(f"Q{item['n']}", q_label_style),
Paragraph(item["q"], ans_q_style)]],
colWidths=[10 * mm, PAGE_W - 2 * MARGIN - 10 * mm],
)
q_row.setStyle(TableStyle([
("BACKGROUND", (0, 0), (0, 0), ACCENT),
("BACKGROUND", (1, 0), (1, 0), LIGHT_TEAL),
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
("LEFTPADDING", (0, 0), (0, 0), 4),
("LEFTPADDING", (1, 0), (1, 0), 6),
("TOPPADDING", (0, 0), (-1, -1), 4),
("BOTTOMPADDING",(0, 0), (-1, -1), 4),
]))
# Replace \n in answers with <br/> for ReportLab
ans_html = item["a"].replace("\n", "<br/>")
ans_box = Table(
[[Paragraph(f"<b>Answer:</b> {ans_html}", ans_text_style)],
[Paragraph(f"Reference: {item['ref']}", ref_style)]],
colWidths=[PAGE_W - 2 * MARGIN],
)
ans_box.setStyle(TableStyle([
("BACKGROUND", (0, 0), (-1, -1), ANSWER_BG),
("LINEAFTER", (0, 0), (0, -1), 3, ANSWER_LINE),
("LEFTPADDING", (0, 0), (-1, -1), 8),
("TOPPADDING", (0, 0), (-1, -1), 4),
("BOTTOMPADDING",(0, 0), (-1, -1), 4),
("BOX", (0, 0), (-1, -1), 0.5, RULE_GRAY),
]))
block = KeepTogether([
q_row,
ans_box,
Spacer(1, 4 * mm),
])
story.append(block)
# ── SOURCES ────────────────────────────────────────────────────────────
story.append(HRFlowable(width="100%", thickness=0.5, color=RULE_GRAY))
story.append(Spacer(1, 2 * mm))
story.append(Paragraph(
"Sources: Gray's Anatomy for Students (Table 8.3) · "
"Color Atlas of Human Anatomy Vol. 2 (p. 134) · "
"Harrison's Principles of Internal Medicine 22E · "
"Adams & Victor's Principles of Neurology 12th Ed.",
ParagraphStyle("FootNote", parent=styles["Normal"], fontSize=7.5,
textColor=colors.HexColor("#888888"),
fontName="Helvetica-Oblique", alignment=TA_CENTER)
))
doc.build(story, onFirstPage=header_footer, onLaterPages=header_footer,
canvasmaker=NumberedCanvas)
print("PDF created successfully.")
if __name__ == "__main__":
import os
os.makedirs("/home/daytona/workspace/dvs-quiz", exist_ok=True)
build_pdf("/home/daytona/workspace/dvs-quiz/Dural_Venous_Sinuses_Quiz.pdf")
python3 /home/daytona/workspace/dvs-quiz/generate_dvs_quiz.py
ls -lh /home/daytona/workspace/dvs-quiz/
Dural Venous Sinuses Quiz
PDF Document · PDF
Summarizer whole of upper limb on one shot
Generate a printable PDF summary card of the upper limb for quick revision
~/upper-limb-summary/generate_summary.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import mm
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, HRFlowable,
Table, TableStyle, PageBreak, KeepTogether
)
from reportlab.lib.enums import TA_CENTER, TA_LEFT
from reportlab.pdfgen import canvas
# ── Palette ──────────────────────────────────────────────────────────────────
NAVY = colors.HexColor("#0F2D4A")
ROYAL = colors.HexColor("#1B5EA8")
SKY = colors.HexColor("#C8DEF5")
LIGHT_SKY = colors.HexColor("#E8F2FC")
ORANGE = colors.HexColor("#C05A20")
GREEN = colors.HexColor("#1A6B3C")
LIGHT_GREEN = colors.HexColor("#E6F4EC")
PURPLE = colors.HexColor("#5C2D91")
LIGHT_PURP = colors.HexColor("#EDE7F6")
RED = colors.HexColor("#B71C1C")
LIGHT_RED = colors.HexColor("#FDECEA")
YELLOW_BG = colors.HexColor("#FFF9E6")
GRAY_BG = colors.HexColor("#F5F5F5")
RULE = colors.HexColor("#CCCCCC")
WHITE = colors.white
PAGE_W, PAGE_H = A4
M = 14 * mm # margin
CW = PAGE_W - 2 * M # content width
# ── Canvas helpers ───────────────────────────────────────────────────────────
class NumberedCanvas(canvas.Canvas):
def __init__(self, *args, **kwargs):
canvas.Canvas.__init__(self, *args, **kwargs)
self._pages = []
def showPage(self):
self._pages.append(dict(self.__dict__))
self._startPage()
def save(self):
n = len(self._pages)
for s in self._pages:
self.__dict__.update(s)
self._draw_footer(n)
canvas.Canvas.showPage(self)
canvas.Canvas.save(self)
def _draw_footer(self, total):
pg = self._pageNumber
self.setFont("Helvetica", 7.5)
self.setFillColor(colors.HexColor("#888888"))
self.drawRightString(PAGE_W - M, 9 * mm, f"Page {pg} of {total}")
self.drawString(M, 9 * mm, "Upper Limb — Quick Revision Summary")
def page_header(c, doc):
c.saveState()
c.setFillColor(NAVY)
c.rect(0, PAGE_H - 13 * mm, PAGE_W, 13 * mm, fill=1, stroke=0)
c.setFillColor(WHITE)
c.setFont("Helvetica-Bold", 11)
c.drawString(M, PAGE_H - 8.5 * mm, "UPPER LIMB")
c.setFont("Helvetica", 9)
c.drawRightString(PAGE_W - M, PAGE_H - 8.5 * mm, "Anatomy Quick Revision Card")
c.setStrokeColor(RULE)
c.setLineWidth(0.4)
c.line(M, 13 * mm, PAGE_W - M, 13 * mm)
c.restoreState()
# ── Style helpers ─────────────────────────────────────────────────────────────
S = getSampleStyleSheet()
def sec_head(text, bg=NAVY, fg=WHITE):
"""Full-width coloured section header."""
p = Paragraph(f"<b>{text}</b>",
ParagraphStyle("sh", parent=S["Normal"], fontSize=9.5,
textColor=fg, fontName="Helvetica-Bold",
leading=13, leftPadding=6, topPadding=3,
bottomPadding=3))
t = Table([[p]], colWidths=[CW])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), bg),
("TOPPADDING", (0,0), (-1,-1), 0),
("BOTTOMPADDING",(0,0),(-1,-1), 0),
("LEFTPADDING", (0,0), (-1,-1), 0),
]))
return t
def sub_head(text, bg=ROYAL):
p = Paragraph(f"<b>{text}</b>",
ParagraphStyle("subh", parent=S["Normal"], fontSize=8.5,
textColor=WHITE, fontName="Helvetica-Bold",
leading=12))
t = Table([[p]], colWidths=[CW])
t.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,-1), bg),
("TOPPADDING", (0,0),(-1,-1), 3),
("BOTTOMPADDING",(0,0),(-1,-1), 3),
("LEFTPADDING", (0,0),(-1,-1), 6),
]))
return t
def body(text, size=8, color=colors.black, indent=0, bg=None):
style = ParagraphStyle("body", parent=S["Normal"], fontSize=size,
textColor=color, fontName="Helvetica",
leading=11.5, leftIndent=indent)
p = Paragraph(text, style)
if bg:
t = Table([[p]], colWidths=[CW])
t.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1), bg),
("TOPPADDING",(0,0),(-1,-1), 2),
("BOTTOMPADDING",(0,0),(-1,-1), 2),
("LEFTPADDING",(0,0),(-1,-1), 6),
]))
return t
return p
def mini_table(headers, rows, col_widths, header_bg=ROYAL, row_bg=WHITE, alt_bg=LIGHT_SKY):
h_style = ParagraphStyle("th", parent=S["Normal"], fontSize=7.5,
textColor=WHITE, fontName="Helvetica-Bold", leading=10)
c_style = ParagraphStyle("td", parent=S["Normal"], fontSize=7.5,
textColor=colors.black, fontName="Helvetica", leading=10)
data = [[Paragraph(h, h_style) for h in headers]]
for i, row in enumerate(rows):
data.append([Paragraph(str(c), c_style) for c in row])
t = Table(data, colWidths=col_widths)
style_cmds = [
("BACKGROUND", (0,0), (-1,0), header_bg),
("VALIGN", (0,0), (-1,-1), "TOP"),
("TOPPADDING", (0,0), (-1,-1), 2),
("BOTTOMPADDING",(0,0),(-1,-1), 2),
("LEFTPADDING",(0,0),(-1,-1), 3),
("GRID", (0,0), (-1,-1), 0.3, RULE),
]
for i in range(1, len(rows)+1):
bg = alt_bg if i % 2 == 0 else row_bg
style_cmds.append(("BACKGROUND",(0,i),(-1,i), bg))
t.setStyle(TableStyle(style_cmds))
return t
def bullet(items, color=NAVY, size=8):
out = []
for item in items:
out.append(Paragraph(f"<font color='#{color.hexval()[2:]}'>●</font> {item}",
ParagraphStyle("bl", parent=S["Normal"], fontSize=size,
fontName="Helvetica", leading=11.5, leftIndent=8)))
return out
def sp(h=2): return Spacer(1, h*mm)
def colored_box(content_list, bg=LIGHT_SKY, border=ROYAL):
inner = Table([[item] for item in content_list], colWidths=[CW - 6])
inner.setStyle(TableStyle([
("TOPPADDING",(0,0),(-1,-1), 1),
("BOTTOMPADDING",(0,0),(-1,-1), 1),
("LEFTPADDING",(0,0),(-1,-1), 0),
("RIGHTPADDING",(0,0),(-1,-1), 0),
]))
outer = Table([[inner]], colWidths=[CW])
outer.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1), bg),
("BOX",(0,0),(-1,-1), 1, border),
("TOPPADDING",(0,0),(-1,-1), 3),
("BOTTOMPADDING",(0,0),(-1,-1), 3),
("LEFTPADDING",(0,0),(-1,-1), 4),
]))
return outer
# ══════════════════════════════════════════════════════════════════════════════
# STORY
# ══════════════════════════════════════════════════════════════════════════════
def build():
import os; os.makedirs("/home/daytona/workspace/upper-limb-summary", exist_ok=True)
out = "/home/daytona/workspace/upper-limb-summary/Upper_Limb_Revision_Card.pdf"
doc = SimpleDocTemplate(out, pagesize=A4,
leftMargin=M, rightMargin=M,
topMargin=18*mm, bottomMargin=18*mm,
title="Upper Limb — Quick Revision Summary")
story = []
# ── COVER TITLE ──────────────────────────────────────────────────────────
story += [
sp(6),
Paragraph("UPPER LIMB", ParagraphStyle("title", parent=S["Title"],
fontSize=26, textColor=NAVY, fontName="Helvetica-Bold",
alignment=TA_CENTER, spaceAfter=2)),
Paragraph("Complete Quick Revision Summary Card",
ParagraphStyle("sub", parent=S["Normal"], fontSize=12,
textColor=ROYAL, fontName="Helvetica",
alignment=TA_CENTER)),
sp(2),
HRFlowable(width="50%", thickness=2, color=ORANGE, hAlign="CENTER"),
sp(4),
Paragraph("Covers: Bones · Joints · Muscles · Brachial Plexus · Nerves · "
"Blood Supply · Spaces · Dermatomes · Clinical Correlations",
ParagraphStyle("cov", parent=S["Normal"], fontSize=9,
textColor=colors.HexColor("#555555"),
fontName="Helvetica-Oblique", alignment=TA_CENTER)),
sp(6),
]
# ═══════════════════════════════════════════════════════════════════════
# SECTION 1 — BONES
# ═══════════════════════════════════════════════════════════════════════
story += [sec_head("1 · BONES"), sp(1)]
# two-column: shoulder girdle | arm + forearm
col = CW / 2 - 2
sg_items = [
Paragraph("<b>Shoulder Girdle</b>", ParagraphStyle("sh2", parent=S["Normal"], fontSize=8, textColor=NAVY, fontName="Helvetica-Bold")),
body("• <b>Clavicle</b>: S-shaped; only bony UL-axial link; medial 2/3 convex ant, lat 1/3 concave"),
body("• <b>Scapula</b>: spine, acromion, coracoid, glenoid cavity, supraspinous/infraspinous/subscapular fossae"),
]
arm_items = [
Paragraph("<b>Arm & Forearm</b>", ParagraphStyle("sh2", parent=S["Normal"], fontSize=8, textColor=NAVY, fontName="Helvetica-Bold")),
body("• <b>Humerus</b>: head, anatomical/surgical necks, GT/LT, deltoid tuberosity, radial groove (radial n. + profunda brachii), med/lat epicondyles, capitulum (radius), trochlea (ulna)"),
body("• <b>Radius</b>: lateral; head, radial tuberosity (biceps), styloid"),
body("• <b>Ulna</b>: medial; olecranon, coronoid process, trochlear notch"),
]
two_col = Table([[sg_items, arm_items]], colWidths=[col, col])
two_col.setStyle(TableStyle([
("VALIGN",(0,0),(-1,-1),"TOP"),
("TOPPADDING",(0,0),(-1,-1),2),("BOTTOMPADDING",(0,0),(-1,-1),2),
("LEFTPADDING",(0,0),(-1,-1),3),
("LINEAFTER",(0,0),(0,-1),0.3,RULE),
]))
story += [two_col, sp(1)]
# Carpals
story += [
sub_head("Carpals — Proximal → Distal", bg=GREEN),
body("<b>Proximal row</b>: Scaphoid · Lunate · Triquetrum · Pisiform"),
body("<b>Distal row</b>: Trapezium · Trapezoid · Capitate · Hamate"),
body('<b>Mnemonic</b>: <font color="#1A6B3C"><b>"She Looks Too Pretty, Try To Catch Her"</b></font>', color=GREEN),
body("<b>Scaphoid</b>: most commonly fractured carpal; check anatomical snuffbox tenderness; risk of AVN"),
sp(2),
]
# ═══════════════════════════════════════════════════════════════════════
# SECTION 2 — JOINTS
# ═══════════════════════════════════════════════════════════════════════
story += [sec_head("2 · JOINTS"), sp(1)]
jt_headers = ["Joint", "Type", "Key Features / Ligaments"]
jt_rows = [
["Sternoclavicular", "Synovial (saddle)", "Only true UL-axial joint; has intra-articular disc; ant/post SC + costoclavicular ligs"],
["Acromioclavicular", "Plane synovial", "AC lig + coracoclavicular lig (conoid + trapezoid); 'shoulder separation'"],
["Glenohumeral", "Ball & socket", "Most mobile & most dislocated; glenoid labrum deepens socket; SITS muscles + GH ligs stabilise"],
["Elbow", "Hinge synovial", "Trochlea + capitulum; UCL (medial) + RCL (lateral); carrying angle ~170° M / ~167° F"],
["Proximal/Distal radioulnar", "Pivot synovial", "Pronation/supination; interosseous membrane connects radius & ulna"],
["Radiocarpal (wrist)", "Ellipsoid synovial", "Distal radius + ulnar disc vs scaphoid/lunate/triquetrum; flex/ext/radial & ulnar deviation"],
]
jt_cw = [CW*0.18, CW*0.17, CW*0.65]
story += [mini_table(jt_headers, jt_rows, jt_cw), sp(2)]
# Cubital fossa + carpal tunnel side by side
cf = [
sub_head("Cubital Fossa", bg=PURPLE),
body("Boundaries: brachioradialis (lat), pronator teres (med), epicondyle line (sup)"),
body("Contents (med→lat): <b>T A N</b>"),
body(" T = Tendon of biceps brachii"),
body(" A = brachial Artery (→ radial + ulnar)"),
body(" N = median Nerve"),
body("(Radial nerve lies just outside, lateral border)"),
]
ct = [
sub_head("Carpal Tunnel", bg=PURPLE),
body("Roof: flexor retinaculum (transverse carpal lig)"),
body("Floor: carpal bones"),
body("Contents (10 structures):"),
body(" 4× FDS tendons + 4× FDP tendons"),
body(" 1× FPL tendon + <b>median nerve</b>"),
body("CTS: compress median n. → thenar wasting + lat 3.5 finger numbness"),
]
twobox = Table([[cf, ct]], colWidths=[CW/2 - 2, CW/2 - 2])
twobox.setStyle(TableStyle([
("VALIGN",(0,0),(-1,-1),"TOP"),
("LINEAFTER",(0,0),(0,-1),0.3,RULE),
("LEFTPADDING",(0,0),(-1,-1),2),
]))
story += [twobox, sp(2)]
# ═══════════════════════════════════════════════════════════════════════
# SECTION 3 — MUSCLES
# ═══════════════════════════════════════════════════════════════════════
story.append(PageBreak())
story += [sec_head("3 · MUSCLES"), sp(1)]
# Rotator cuff highlight box
rc_content = [
Paragraph("<b>ROTATOR CUFF — SITS</b>", ParagraphStyle("rc", parent=S["Normal"],
fontSize=9, textColor=ORANGE, fontName="Helvetica-Bold")),
]
rc_rows = [
["<b>S</b>upraspinatus", "Initiates abduction 0–15°", "Suprascapular (C5,C6)", "Most commonly torn"],
["<b>I</b>nfraspinatus", "Lateral rotation", "Suprascapular (C5,C6)", "—"],
["<b>T</b>eres minor", "Lateral rotation", "Axillary (C5,C6)", "—"],
["<b>S</b>ubscapularis", "Medial rotation", "Upper/lower subscapular", "Only anterior muscle"],
]
rc_cw = [CW*0.22, CW*0.28, CW*0.28, CW*0.22]
rc_content.append(mini_table(["Muscle","Action","Nerve","Note"], rc_rows, rc_cw,
header_bg=ORANGE, alt_bg=YELLOW_BG))
story += [colored_box(rc_content, bg=YELLOW_BG, border=ORANGE), sp(2)]
# Arm muscles table
story += [sub_head("Arm Compartments"), sp(1)]
arm_rows = [
["Biceps brachii", "Anterior", "Flex elbow, supinate forearm, weak shoulder flex", "Musculocutaneous C5,C6"],
["Brachialis", "Anterior", "Pure elbow flexion ('workhorse')", "Musculocutaneous + radial C5,C6"],
["Coracobrachialis", "Anterior", "Arm flexion + adduction", "Musculocutaneous C6,C7"],
["Triceps brachii", "Posterior", "Elbow extension; long head extends/adducts arm", "Radial C6–C8"],
["Anconeus", "Posterior", "Assists elbow extension", "Radial C7,C8"],
]
arm_cw = [CW*0.2, CW*0.13, CW*0.42, CW*0.25]
story += [mini_table(["Muscle","Compartment","Action","Nerve"], arm_rows, arm_cw), sp(2)]
# Forearm muscles — compact two-panel
story += [sub_head("Forearm — Anterior (Flexors/Pronators)"), sp(1)]
fa_ant = [
body("<b>Superficial</b> (lateral epicondyle/medial epicondyle origin):"),
body("Pronator teres · Flexor carpi radialis · Palmaris longus · Flexor carpi ulnaris · FDS", indent=8),
body("<b>Deep</b>: FDP · FPL · Pronator quadratus", indent=0),
body("FDS → flexes PIP | FDP → flexes DIP | FPL → flexes thumb IP", color=PURPLE),
]
story += fa_ant + [sp(1)]
story += [sub_head("Forearm — Posterior (Extensors/Supinators)"), sp(1)]
fa_post = [
body("<b>Superficial</b>: Brachioradialis · ECRL · ECRB · ED · EDM · ECU · Anconeus"),
body("<b>Deep</b>: Supinator · APL · EPB · EPL · Extensor indicis", indent=0),
]
story += fa_post + [sp(2)]
# Intrinsic hand
story += [sub_head("Intrinsic Hand Muscles"), sp(1)]
hand_rows = [
["Thenar (3)", "Abd pollicis brevis, Opponens pollicis, FPB", "Opposition/abduction/flexion of thumb", "Recurrent median nerve"],
["Hypothenar (3)", "Abd digiti minimi, Opponens DM, FDM", "Little finger movements", "Deep branch ulnar"],
["Lumbricals (4)", "1st & 2nd = median; 3rd & 4th = ulnar", "Flex MCP, extend IP ('intrinsic plus')", "Median + Ulnar"],
["Dorsal interossei (4)", "Ulnar nerve", "Abduct fingers — DAB", "Ulnar"],
["Palmar interossei (3)", "Ulnar nerve", "Adduct fingers — PAD", "Ulnar"],
["Adductor pollicis", "Ulnar (deep branch)", "Adducts thumb; Froment's sign if lost", "Ulnar"],
]
hand_cw = [CW*0.18, CW*0.27, CW*0.33, CW*0.22]
story += [mini_table(["Group","Muscles","Action","Nerve"], hand_rows, hand_cw), sp(2)]
# ═══════════════════════════════════════════════════════════════════════
# SECTION 4 — BRACHIAL PLEXUS
# ═══════════════════════════════════════════════════════════════════════
story.append(PageBreak())
story += [sec_head("4 · BRACHIAL PLEXUS"), sp(1)]
story += [
colored_box([
Paragraph('<b>Mnemonic:</b> <font color="#C05A20"><b>R</b></font>obert '
'<font color="#C05A20"><b>T</b></font>aylor '
'<font color="#C05A20"><b>D</b></font>rinks '
'<font color="#C05A20"><b>C</b></font>old '
'<font color="#C05A20"><b>B</b></font>eer → '
'Roots · Trunks · Divisions · Cords · Branches',
ParagraphStyle("mn", parent=S["Normal"], fontSize=9,
fontName="Helvetica-Bold", textColor=NAVY)),
], bg=YELLOW_BG, border=ORANGE),
sp(2),
]
bp_rows = [
["Roots", "C5, C6, C7, C8, T1 (anterior rami)"],
["Trunks", "Superior (C5+C6) · Middle (C7) · Inferior (C8+T1)"],
["Divisions", "Each trunk → anterior + posterior (6 divisions total)"],
["Cords", "Lateral (ant sup+mid) · Posterior (all post) · Medial (ant inf)\n— named by relation to axillary artery —"],
["Terminal branches", "Musculocutaneous · Axillary · Radial · Median (2 roots) · Ulnar"],
]
bp_cw = [CW*0.2, CW*0.8]
story += [mini_table(["Level","Components"], bp_rows, bp_cw), sp(1)]
story += [sub_head("Key Pre-terminal Branches", bg=colors.HexColor("#37474F")), sp(1)]
pre = [
body("<b>From Roots</b>: Dorsal scapular (C5) → rhomboids/levator scap; Long thoracic (C5-C7) → serratus anterior"),
body("<b>From Superior Trunk</b>: Suprascapular → supraspinatus/infraspinatus; Nerve to subclavius"),
body("<b>From Lateral Cord</b>: Lateral pectoral; Musculocutaneous; Lateral root of median"),
body("<b>From Medial Cord</b>: Medial pectoral; Med cut. nerves arm/forearm; Medial root of median; Ulnar"),
body("<b>From Posterior Cord</b>: Upper/lower subscapular; Thoracodorsal (latissimus dorsi); Axillary; Radial"),
]
story += pre + [sp(2)]
# ═══════════════════════════════════════════════════════════════════════
# SECTION 5 — NERVES (injury signs)
# ═══════════════════════════════════════════════════════════════════════
story += [sec_head("5 · NERVE INJURIES — Signs & Mnemonics"), sp(1)]
nerve_data = [
{
"name": "AXILLARY NERVE (C5, C6)",
"supplies": "Deltoid, teres minor; skin over lateral arm",
"injured": "Surgical neck # humerus; anterior GH dislocation",
"signs": "Loss of shoulder abduction; flat shoulder; numbness 'regimental badge' area",
"bg": SKY, "border": ROYAL
},
{
"name": "MUSCULOCUTANEOUS NERVE (C5–C7)",
"supplies": "Coracobrachialis, biceps, brachialis; skin lateral forearm",
"injured": "Axilla injury (rare)",
"signs": "Weak elbow flexion + supination; numbness lateral forearm",
"bg": LIGHT_SKY, "border": ROYAL
},
{
"name": "RADIAL NERVE (C5–C8, T1)",
"supplies": "All posterior compartments arm+forearm; skin posterior arm/forearm + dorsum hand lat 3.5",
"injured": "Mid-shaft humeral # (radial groove); Saturday night palsy",
"signs": "WRIST DROP — loss wrist + finger extension; loss brachioradialis; triceps spared (branches above groove); dorsum hand numbness",
"bg": LIGHT_GREEN, "border": GREEN
},
{
"name": "MEDIAN NERVE (C6–C8, T1)",
"supplies": "Most ant forearm; LOAF hand muscles; palmar lat 3.5 digits",
"injured": "Carpal tunnel (low); supracondylar # (high)",
"signs": "HIGH: Hand of benediction; ape hand (thenar wasting); can't flex index/middle | LOW (CTS): Thenar wasting; opposition lost; lat 3.5 finger numbness; +ve Tinel's & Phalen's",
"bg": LIGHT_PURP, "border": PURPLE
},
{
"name": "ULNAR NERVE (C7–T1)",
"supplies": "FCU, medial FDP; ALL intrinsic hand except LOAF; skin medial 1.5 digits",
"injured": "Medial epicondyle # / cubital tunnel; Guyon's canal (wrist)",
"signs": "CLAW HAND (ring + little fingers); ulnar paradox; loss finger ABduction; Froment's sign (FPL compensates weak adductor pollicis)",
"bg": LIGHT_RED, "border": RED
},
]
for nd in nerve_data:
content = [
Paragraph(f"<b>{nd['name']}</b>",
ParagraphStyle("nh", parent=S["Normal"], fontSize=8.5,
textColor=NAVY, fontName="Helvetica-Bold")),
body(f"<b>Supplies:</b> {nd['supplies']}"),
body(f"<b>Injured by:</b> {nd['injured']}"),
body(f"<b>Signs:</b> {nd['signs']}", color=RED),
]
story += [colored_box(content, bg=nd["bg"], border=nd["border"]), sp(1.5)]
story += [
colored_box([
Paragraph("<b>LOAF mnemonic</b> (muscles lost in LOW median nerve injury):",
ParagraphStyle("loaf", parent=S["Normal"], fontSize=8.5,
textColor=PURPLE, fontName="Helvetica-Bold")),
body("<b>L</b>umbricals 1 & 2 · <b>O</b>pponens pollicis · <b>A</b>bductor pollicis brevis · <b>F</b>lexor pollicis brevis"),
], bg=LIGHT_PURP, border=PURPLE),
sp(2),
]
# ═══════════════════════════════════════════════════════════════════════
# SECTION 6 — BLOOD SUPPLY
# ═══════════════════════════════════════════════════════════════════════
story.append(PageBreak())
story += [sec_head("6 · BLOOD SUPPLY"), sp(1)]
art_content = [
sub_head("Arterial Tree", bg=colors.HexColor("#B71C1C")),
body("Subclavian a. → <b>Axillary a.</b> (3 parts divided by pec minor)"),
body(" 1st part: Superior thoracic a.", indent=8),
body(" 2nd part: Thoracoacromial · Lateral thoracic", indent=8),
body(" 3rd part: Subscapular (→circumflex scapular + thoracodorsal) · Ant/Post circumflex humeral", indent=8),
body("→ <b>Brachial a.</b> (main artery of arm)"),
body(" → Profunda brachii (radial groove; with radial nerve)", indent=8),
body(" → Bifurcates in <b>cubital fossa</b> →", indent=8),
body(" <b>Radial a.</b> → superficial palmar branch → deep palmar arch (mainly radial)", indent=16),
body(" <b>Ulnar a.</b> → ant/post interosseous aa. → superficial palmar arch (mainly ulnar)", indent=16),
sp(1),
body("<b>Superficial palmar arch</b> (mainly ulnar) → common digital arteries → digits 2–5"),
body("<b>Deep palmar arch</b> (mainly radial) → palmar metacarpal arteries"),
]
story += art_content + [sp(2)]
vein_content = [
sub_head("Superficial Veins (Clinically Important)", bg=ROYAL),
body("• <b>Cephalic vein</b>: lateral side; dorsal venous arch → lateral forearm → deltopectoral groove → axillary vein"),
body("• <b>Basilic vein</b>: medial side → pierces deep fascia mid-arm → becomes axillary vein"),
body("• <b>Median cubital vein</b>: connects cephalic + basilic at <b>cubital fossa</b> — standard venepuncture site"),
]
story += vein_content + [sp(2)]
# ═══════════════════════════════════════════════════════════════════════
# SECTION 7 — KEY SPACES
# ═══════════════════════════════════════════════════════════════════════
story += [sec_head("7 · KEY SPACES & OPENINGS"), sp(1)]
sp_rows = [
["Quadrangular space", "Teres minor (sup), teres major (inf), long head triceps (med), surgical neck (lat)", "Axillary nerve + post circumflex humeral a."],
["Triangular space", "Teres minor (sup), teres major (inf), long head triceps (lat)", "Circumflex scapular artery"],
["Triangular interval", "Teres major (sup), long head triceps (med), lateral head triceps (lat)", "Radial nerve + profunda brachii a."],
["Axilla", "Clavicle (ant), scapula (post), chest wall (med), humerus (lat)", "Brachial plexus cords, axillary a. & v., axillary lymph nodes"],
["Guyon's canal", "Pisiform (med), hook of hamate (lat), palmar carpal lig (roof)", "Ulnar nerve + ulnar artery"],
]
sp_cw = [CW*0.2, CW*0.45, CW*0.35]
story += [mini_table(["Space","Boundaries","Contents"], sp_rows, sp_cw), sp(2)]
# ═══════════════════════════════════════════════════════════════════════
# SECTION 8 — DERMATOMES
# ═══════════════════════════════════════════════════════════════════════
story += [sec_head("8 · DERMATOMES"), sp(1)]
derm_rows = [
["C5", "Lateral arm (regimental badge area)", "Axillary nerve"],
["C6", "Lateral forearm, thumb, index finger", "Musculocutaneous / median"],
["C7", "Middle finger", "Median (middle = C7)"],
["C8", "Ring & little finger, medial forearm", "Ulnar"],
["T1", "Medial arm", "Medial cutaneous nerve of arm"],
]
derm_cw = [CW*0.1, CW*0.5, CW*0.4]
story += [mini_table(["Root","Region","Nerve"], derm_rows, derm_cw), sp(2)]
# ═══════════════════════════════════════════════════════════════════════
# SECTION 9 — CLINICAL CORRELATIONS
# ═══════════════════════════════════════════════════════════════════════
story += [sec_head("9 · CLINICAL CORRELATIONS", bg=RED), sp(1)]
clin_rows = [
["Surgical neck # humerus", "Axillary n.", "Loss abduction; flat shoulder; regimental badge numbness"],
["Mid-shaft humeral #", "Radial n. (radial groove)", "Wrist drop; spared triceps"],
["Supracondylar # (children)", "Ant interosseous n. (median)", "Cannot make 'OK sign'"],
["Medial epicondyle #", "Ulnar n.", "Claw hand (ring + little)"],
["Anterior GH dislocation", "Axillary n.", "Same as surgical neck #"],
["Winged scapula", "Long thoracic n. (C5-C7)", "Serratus ant. palsy; scapula protrudes on wall push"],
["Carpal tunnel syndrome", "Median n. at wrist", "Thenar wasting; lat 3.5 finger numbness; Tinel's; Phalen's"],
["Erb's palsy (C5, C6)", "Upper brachial plexus", "Waiter's tip: arm adducted/medially rotated, elbow extended, forearm pronated"],
["Klumpke's palsy (C8, T1)", "Lower brachial plexus", "Claw hand; Horner's syndrome if sympathetics involved"],
["Rotator cuff tear", "Supraspinatus (most common)", "Painful arc 60–120°; weak abduction"],
["Scaphoid #", "Avascular (not nerve)", "Anatomical snuffbox tenderness; risk of AVN of proximal fragment"],
["Dupuytren's contracture", "Palmar fascia (not nerve)", "Flexion contracture ring > little finger; palmar nodule"],
]
clin_cw = [CW*0.25, CW*0.22, CW*0.53]
story += [mini_table(["Condition","Nerve/Structure","Key Sign / Note"], clin_rows, clin_cw,
header_bg=RED, alt_bg=LIGHT_RED), sp(2)]
# ═══════════════════════════════════════════════════════════════════════
# SOURCES
# ═══════════════════════════════════════════════════════════════════════
story += [
HRFlowable(width="100%", thickness=0.4, color=RULE),
sp(1),
Paragraph(
"Sources: Gray's Anatomy for Students (Ch. 7) · Rosen's Emergency Medicine · "
"Neuroanatomy through Clinical Cases 3rd Ed. · Color Atlas of Human Anatomy Vol. 2",
ParagraphStyle("src", parent=S["Normal"], fontSize=7,
textColor=colors.HexColor("#888888"),
fontName="Helvetica-Oblique", alignment=TA_CENTER)),
]
doc.build(story, onFirstPage=page_header, onLaterPages=page_header,
canvasmaker=NumberedCanvas)
print("Done:", out)
if __name__ == "__main__":
build()
python3 /home/daytona/workspace/upper-limb-summary/generate_summary.py
ls -lh /home/daytona/workspace/upper-limb-summary/
Upper Limb Revision Card
PDF Document · PDF
| Section | Color | Content |
|---|---|---|
| 1 · Bones | Navy/Green | Shoulder girdle, humerus, forearm, carpals with mnemonic |
| 2 · Joints | Navy/Purple | All 6 joints in a table + cubital fossa (TAN) + carpal tunnel side-by-side |
| 3 · Muscles | Orange/Blue | Rotator cuff SITS highlight box, arm, forearm, intrinsic hand tables |
| 4 · Brachial Plexus | Navy/Orange | RTDCB mnemonic, full table, all pre-terminal branches |
| 5 · Nerve Injuries | Colour-coded | One box per nerve - supplies, injured by, signs; LOAF mnemonic |
| 6 · Blood Supply | Red/Blue | Full arterial tree + superficial veins |
| 7 · Key Spaces | Navy | Quadrangular, triangular, axilla, Guyon's canal |
| 8 · Dermatomes | Navy | C5-T1 table |
| 9 · Clinical Correlations | Red | 12 conditions with nerve and key sign |