Usg basic reading step wise with images
| Transducer | Frequency | Field Shape | Best For |
|---|---|---|---|
| Linear (high-freq) | 5-13 MHz | Rectangular | Veins, arteries, tendons, thyroid, breast |
| Curvilinear (low-freq) | 2-5 MHz | Fan/wide arc | Abdomen, kidneys, liver, bladder |
| Phased array (low-freq) | 1-5 MHz | Pie/sector | Heart (fits between ribs), lungs |

For abdominal exams: point the probe marker toward the patient's head for longitudinal views. This puts the superior pole on the LEFT of your screen and inferior pole on the RIGHT.
For cardiac echo: the screen indicator is on the right side - the exception to the rule.
| Term | Appearance | Examples |
|---|---|---|
| Anechoic | Black (no echoes) | Urine, blood, bile, simple cysts |
| Hyperechoic | Bright white (strong reflection) | Bone, diaphragm, stones, calcifications |
| Hypoechoic | Darker grey than surroundings | Renal cortex vs liver |
| Isoechoic | Same brightness as surroundings | Cortex = medulla in chronic kidney disease |
| Echogenic | General term for "bright/white" | Used for reflective structures |


| Movement | Description | Used For |
|---|---|---|
| Sliding | Moving the probe across the skin | Survey a larger area |
| Tilting/Fanning | Angling the probe while keeping footprint in place | Opening up a view, getting behind structures |
| Rotating | Spinning the probe on its axis | Switching between long-axis and short-axis |
| Rocking | Tilting along the plane of view | Extending the image up or down |
| Compression | Pressing down to displace bowel gas | Improves abdominal views |

| View | Probe Position | What You See |
|---|---|---|
| Parasternal long axis (PLAX) | Left sternal border, 3rd-4th ICS | LV, MV, AV, aorta, LA |
| Parasternal short axis (PSAX) | Same position, rotate 90° | Cross-section of LV, papillary muscles, RV |
| Apical 4-chamber | Cardiac apex (4th-5th ICS) | All 4 chambers, both AV valves |
| Subcostal | Below xiphoid, through liver | Heart + pericardial effusion |
Create a study guide PDF for USG physics and probe selection
node /home/daytona/skills/shared/scripts/fetch_images.js \ "https://cdn.orris.care/cdss_images/b8c49d5353cfaad1c09f50c9da1367230cd4908ec63686425176683d1809c120.png" \ "https://cdn.orris.care/cdss_images/42b47ed25f93a9d3294b20aa73c6fb370656e68b5a0203efc5aa25699bc05e57.png" \ "https://cdn.orris.care/cdss_images/985702c67b5b62b30c7b11d1aecca5cef4aa4f31e7caddb5931a05b82ad9ede2.png" \ "https://cdn.orris.care/cdss_images/19845805e44c505b39d3a7d206e575bf7866c9450c72dabd45232311f55b94e4.png" 2>/dev/null | python3 -c " import json, sys, base64, os data = json.load(sys.stdin) os.makedirs('/home/daytona/workspace/usg-study-guide/images', exist_ok=True) names = ['resolution.png', 'shadowing_enhancement.png', 'reverberation.png', 'cardiac_views.png'] for i, item in enumerate(data): if item.get('base64'): raw = base64.b64decode(item['base64'].split(',')[1]) path = f'/home/daytona/workspace/usg-study-guide/images/{names[i]}' with open(path, 'wb') as f: f.write(raw) print(f'Saved: {path} ({len(raw)} bytes)') else: print(f'FAILED: {item}') "
~/usg-study-guide/generate_pdf.py
#!/usr/bin/env python3
"""
USG Physics & Probe Selection - Study Guide PDF Generator
"""
import os
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import mm, cm
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_JUSTIFY
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
PageBreak, Image, HRFlowable, KeepTogether
)
from reportlab.platypus.flowables import BalancedColumns
from reportlab.graphics.shapes import Drawing, Rect, String, Line
from reportlab.graphics import renderPDF
# ── Paths ──────────────────────────────────────────────────────────────────
OUT_DIR = "/home/daytona/workspace/usg-study-guide/output"
IMG_DIR = "/home/daytona/workspace/usg-study-guide/images"
OUT_FILE = os.path.join(OUT_DIR, "USG_Physics_ProbeSelection_StudyGuide.pdf")
os.makedirs(OUT_DIR, exist_ok=True)
# ── Colour Palette ─────────────────────────────────────────────────────────
NAVY = colors.HexColor("#0D2B55")
TEAL = colors.HexColor("#007C9E")
LIGHT_BLU = colors.HexColor("#D6EEF5")
AMBER = colors.HexColor("#E8920A")
AMBER_LT = colors.HexColor("#FEF3DC")
GREEN = colors.HexColor("#1A7A4A")
GREEN_LT = colors.HexColor("#D6F0E2")
GRAY_LT = colors.HexColor("#F4F6F8")
GRAY_MID = colors.HexColor("#CBD5E0")
WHITE = colors.white
BLACK = colors.black
W, H = A4 # 595.28 x 841.89 pt
MARGIN = 1.8 * cm
# ── Document ───────────────────────────────────────────────────────────────
doc = SimpleDocTemplate(
OUT_FILE,
pagesize=A4,
leftMargin=MARGIN, rightMargin=MARGIN,
topMargin=MARGIN, bottomMargin=MARGIN,
title="USG Physics & Probe Selection Study Guide",
author="Medical Library - Orris"
)
# ── Styles ─────────────────────────────────────────────────────────────────
base = getSampleStyleSheet()
def style(name, parent="Normal", **kw):
s = ParagraphStyle(name, parent=base[parent], **kw)
return s
COVER_TITLE = style("CoverTitle", "Title",
fontSize=30, textColor=WHITE, alignment=TA_CENTER,
spaceAfter=8, leading=36)
COVER_SUB = style("CoverSub", "Normal",
fontSize=13, textColor=colors.HexColor("#B0D4E8"),
alignment=TA_CENTER, spaceAfter=4)
COVER_BADGE = style("CoverBadge", "Normal",
fontSize=10, textColor=WHITE,
alignment=TA_CENTER)
H1 = style("H1", "Heading1",
fontSize=16, textColor=WHITE, leading=20,
spaceAfter=0, spaceBefore=0,
backColor=NAVY)
H2 = style("H2", "Heading2",
fontSize=12, textColor=NAVY, leading=16,
spaceBefore=10, spaceAfter=4,
borderPad=2)
H3 = style("H3", "Heading3",
fontSize=10, textColor=TEAL, leading=13,
spaceBefore=6, spaceAfter=2)
BODY = style("Body", "Normal",
fontSize=9.5, leading=14, spaceAfter=5,
alignment=TA_JUSTIFY)
BODY_SM = style("BodySm", "Normal",
fontSize=8.5, leading=12, spaceAfter=3)
BULLET = style("Bullet", "Normal",
fontSize=9.5, leading=13, spaceAfter=3,
leftIndent=14, firstLineIndent=-10)
KEY_TERM = style("KeyTerm", "Normal",
fontSize=9.5, leading=14, textColor=NAVY,
spaceAfter=3)
BOX_TITLE = style("BoxTitle", "Normal",
fontSize=10, textColor=WHITE, leading=14,
alignment=TA_LEFT)
BOX_BODY = style("BoxBody", "Normal",
fontSize=9, leading=13, spaceAfter=3,
leftIndent=6)
CAPTION = style("Caption", "Normal",
fontSize=8, textColor=colors.HexColor("#555555"),
alignment=TA_CENTER, leading=11, spaceAfter=4)
TABLE_HDR = style("TblHdr", "Normal",
fontSize=9, textColor=WHITE, leading=12,
alignment=TA_CENTER)
TABLE_CELL = style("TblCell", "Normal",
fontSize=8.5, leading=12)
TABLE_CELL_C = style("TblCellC", "Normal",
fontSize=8.5, leading=12, alignment=TA_CENTER)
FOOTER_STYLE = style("Footer", "Normal",
fontSize=7.5, textColor=colors.HexColor("#888888"),
alignment=TA_CENTER)
# ── Helper flowables ───────────────────────────────────────────────────────
def section_header(title, color=NAVY):
data = [[Paragraph(f"<b>{title}</b>", H1)]]
t = Table(data, colWidths=[W - 2*MARGIN])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), color),
("TOPPADDING", (0,0), (-1,-1), 6),
("BOTTOMPADDING", (0,0), (-1,-1), 6),
("LEFTPADDING", (0,0), (-1,-1), 10),
("RIGHTPADDING", (0,0), (-1,-1), 10),
("ROUNDEDCORNERS", [4]),
]))
return t
def colored_box(title, body_paragraphs, bg=LIGHT_BLU, title_bg=TEAL, border=TEAL):
inner = [Paragraph(f"<b>{title}</b>", BOX_TITLE)] + body_paragraphs
data = [[p] for p in inner]
rows = len(data)
t = Table([[p] for p in inner], colWidths=[W - 2*MARGIN - 4])
style_cmds = [
("BACKGROUND", (0,0), (0,0), title_bg),
("BACKGROUND", (0,1), (-1,-1), bg),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 8),
("RIGHTPADDING", (0,0), (-1,-1), 8),
("BOX", (0,0), (-1,-1), 0.8, border),
("ROUNDEDCORNERS",[4]),
]
t.setStyle(TableStyle(style_cmds))
return t
def hr(color=GRAY_MID, thickness=0.8):
return HRFlowable(width="100%", thickness=thickness, color=color,
spaceAfter=4, spaceBefore=4)
def bullet(text):
return Paragraph(f"<bullet>•</bullet> {text}", BULLET)
def sp(n=6):
return Spacer(1, n)
def img(name, width_mm, caption_text=""):
path = os.path.join(IMG_DIR, name)
if not os.path.exists(path):
return Paragraph(f"[Image missing: {name}]", CAPTION)
items = []
target_w = width_mm * mm
im = Image(path, width=target_w)
im.hAlign = "CENTER"
items.append(im)
if caption_text:
items.append(Paragraph(caption_text, CAPTION))
return items
# ── PAGE TEMPLATES (header/footer) ─────────────────────────────────────────
def on_page(canvas, doc):
canvas.saveState()
# Top strip
canvas.setFillColor(NAVY)
canvas.rect(0, H - 22, W, 22, fill=1, stroke=0)
canvas.setFillColor(WHITE)
canvas.setFont("Helvetica-Bold", 9)
canvas.drawString(MARGIN, H - 14, "USG Physics & Probe Selection")
canvas.setFont("Helvetica", 8)
canvas.drawRightString(W - MARGIN, H - 14, "Study Guide")
# Bottom strip
canvas.setFillColor(NAVY)
canvas.rect(0, 0, W, 16, fill=1, stroke=0)
canvas.setFillColor(WHITE)
canvas.setFont("Helvetica", 7.5)
canvas.drawString(MARGIN, 5, "Source: Murray & Nadel | Rosen's Emergency Medicine | Comprehensive Clinical Nephrology")
canvas.drawRightString(W - MARGIN, 5, f"Page {doc.page}")
canvas.restoreState()
def on_first_page(canvas, doc):
canvas.saveState()
# Full cover background
canvas.setFillColor(NAVY)
canvas.rect(0, 0, W, H, fill=1, stroke=0)
# Teal accent band
canvas.setFillColor(TEAL)
canvas.rect(0, H*0.62, W, 8, fill=1, stroke=0)
canvas.rect(0, H*0.38, W, 8, fill=1, stroke=0)
canvas.restoreState()
# ── STORY ──────────────────────────────────────────────────────────────────
story = []
# ═══════════════════════════════════════════════════════════════════════════
# COVER PAGE
# ═══════════════════════════════════════════════════════════════════════════
# Big white logo-like top space
story.append(Spacer(1, 5.5*cm))
cover_title_data = [[Paragraph(
'<font color="#FFFFFF"><b>ULTRASOUND</b></font>',
style("CoverBig","Normal",fontSize=42,textColor=WHITE,alignment=TA_CENTER,leading=50))
]]
ct = Table(cover_title_data, colWidths=[W - 2*MARGIN])
ct.setStyle(TableStyle([("BACKGROUND",(0,0),(-1,-1),colors.transparent),
("TOPPADDING",(0,0),(-1,-1),0),
("BOTTOMPADDING",(0,0),(-1,-1),0)]))
story.append(ct)
story.append(Spacer(1, 0.3*cm))
story.append(Paragraph("PHYSICS & PROBE SELECTION", COVER_TITLE))
story.append(Spacer(1, 0.5*cm))
story.append(Paragraph("A Complete Step-by-Step Study Guide", COVER_SUB))
story.append(Paragraph("With Annotated Diagrams & Clinical Pearls", COVER_SUB))
story.append(Spacer(1, 1.5*cm))
# Badge row
badges = [
["PULSE-ECHO\nPRINCIPLE", "FREQUENCY &\nWAVELENGTH", "3 PROBE\nTYPES", "RESOLUTION\n& DEPTH", "DOPPLER\nMODES"],
]
badge_table = Table(badges, colWidths=[(W-2*MARGIN)/5]*5)
badge_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), TEAL),
("TEXTCOLOR", (0,0), (-1,-1), WHITE),
("FONTNAME", (0,0), (-1,-1), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 7.5),
("ALIGN", (0,0), (-1,-1), "CENTER"),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("TOPPADDING", (0,0), (-1,-1), 8),
("BOTTOMPADDING", (0,0), (-1,-1), 8),
("GRID", (0,0), (-1,-1), 0.5, NAVY),
]))
story.append(badge_table)
story.append(Spacer(1, 1.5*cm))
story.append(Paragraph("Based on Murray & Nadel | Rosen's Emergency Medicine | Comprehensive Clinical Nephrology", COVER_BADGE))
story.append(Spacer(1, 0.3*cm))
story.append(Paragraph("Pfenninger & Fowler's Procedures for Primary Care", COVER_BADGE))
story.append(PageBreak())
# ═══════════════════════════════════════════════════════════════════════════
# SECTION 1: PHYSICS FUNDAMENTALS
# ═══════════════════════════════════════════════════════════════════════════
story.append(sp(10))
story.append(section_header("SECTION 1 | PHYSICS FUNDAMENTALS"))
story.append(sp(8))
story.append(Paragraph("1.1 The Pulse-Echo Principle", H2))
story.append(Paragraph(
"All diagnostic ultrasound is based on the <b>pulse-echo principle</b>. A short burst of sound "
"is emitted from the transducer into tissue. Echoes return to the transducer after bouncing "
"off tissue interfaces. The machine times the round-trip of each echo and uses that delay to "
"calculate <b>depth</b>, building a 2D image line by line.",
BODY))
story.append(Paragraph(
"Tissues that are very elastic, dense, or compressible tend to <b>transmit</b> sound waves. "
"Inelastic, less dense, or non-compressible tissues tend to <b>reflect</b> them. "
"The stronger the reflection, the brighter the signal on screen.",
BODY))
# Key steps box
story.append(colored_box(
" Pulse-Echo Steps",
[
bullet("Transducer emits a <b>short ultrasound pulse</b> into tissue"),
bullet("Sound encounters tissue interfaces with different <b>acoustic impedance</b>"),
bullet("Part of the sound is <b>reflected</b> back as an echo; part continues deeper"),
bullet("The transducer records the returning echo and logs its <b>arrival time</b>"),
bullet("Distance = (Speed of sound in tissue x Time) / 2 <font color='#007C9E'>[speed ~1540 m/s in soft tissue]</font>"),
bullet("The machine plots echo amplitude as <b>brightness</b> at the calculated depth"),
],
bg=LIGHT_BLU, title_bg=TEAL
))
story.append(sp(8))
story.append(Paragraph("1.2 Frequency and Wavelength", H2))
story.append(Paragraph(
"Modern diagnostic ultrasound operates between <b>1-18 MHz</b> (above the 20,000 Hz limit of "
"human hearing). The relationship between frequency and wavelength is the most critical "
"concept in probe selection.",
BODY))
# Frequency table
freq_data = [
[Paragraph("<b>Property</b>", TABLE_HDR),
Paragraph("<b>Higher Frequency\n(eg 10-15 MHz)</b>", TABLE_HDR),
Paragraph("<b>Lower Frequency\n(eg 2-5 MHz)</b>", TABLE_HDR)],
["Resolution",
Paragraph("BETTER - shorter wavelength, finer detail", TABLE_CELL),
Paragraph("WORSE - longer wavelength, less detail", TABLE_CELL)],
["Penetration",
Paragraph("WORSE - attenuated quickly in tissue", TABLE_CELL),
Paragraph("BETTER - reaches deeper structures", TABLE_CELL)],
["Best for",
Paragraph("Superficial structures (veins, thyroid, tendons)", TABLE_CELL),
Paragraph("Deep structures (abdomen, heart, kidneys)", TABLE_CELL)],
["Wavelength",
Paragraph("Short (eg 0.1-0.3 mm)", TABLE_CELL),
Paragraph("Long (eg 0.3-1.5 mm)", TABLE_CELL)],
]
ft = Table(freq_data, colWidths=[(W-2*MARGIN)*0.22, (W-2*MARGIN)*0.39, (W-2*MARGIN)*0.39])
ft.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), NAVY),
("BACKGROUND", (0,1), (-1,1), GRAY_LT),
("BACKGROUND", (0,2), (-1,2), WHITE),
("BACKGROUND", (0,3), (-1,3), GRAY_LT),
("BACKGROUND", (0,4), (-1,4), WHITE),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (0,-1), "Helvetica-Bold"),
("FONTSIZE", (0,0), (0,-1), 8.5),
("TEXTCOLOR", (0,0), (0,-1), NAVY),
("ALIGN", (0,0), (0,-1), "LEFT"),
("GRID", (0,0), (-1,-1), 0.4, GRAY_MID),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
story.append(ft)
story.append(sp(8))
# Resolution diagram
story.append(Paragraph("1.3 Axial vs. Lateral Resolution", H2))
story.append(Paragraph(
"<b>Axial resolution</b> is the ability to distinguish two structures lying along the axis of "
"the beam (one deeper than the other). It depends entirely on <b>frequency</b> - higher "
"frequency = shorter pulse = better axial resolution.",
BODY))
story.append(Paragraph(
"<b>Lateral resolution</b> is the ability to distinguish two structures lying side by side "
"(perpendicular to the beam). It is best within the <b>focal zone</b> where the beam is "
"narrowest, and degrades beyond it (far field).",
BODY))
res_imgs = img("resolution.png", 130,
"Figure 1. Axial resolution (top): higher frequency separates the two structures clearly. "
"Lateral resolution (bottom): both structures are distinct within the focal zone (*), but "
"blur together in the far field beyond it. (Murray & Nadel's Textbook of Respiratory Medicine)")
for item in res_imgs:
story.append(item)
story.append(sp(6))
# Clinical pearl
story.append(colored_box(
" Clinical Pearl: The Frequency-Depth Tradeoff",
[
Paragraph(
"When you <b>increase frequency</b> for better detail, you <b>sacrifice depth</b>. "
"Always choose the highest frequency that still gives you adequate tissue penetration "
"for the structure you need to see. If the image looks dark or the structure is cut "
"off at the bottom, switch to a lower frequency probe.",
BOX_BODY),
],
bg=AMBER_LT, title_bg=AMBER
))
story.append(sp(8))
story.append(Paragraph("1.4 Acoustic Impedance", H2))
story.append(Paragraph(
"Acoustic impedance (Z) = tissue density x speed of sound in that tissue. "
"When sound crosses an interface between two tissues with <b>different impedance</b>, "
"some energy is reflected (creating an echo) and some continues deeper (transmission). "
"The greater the impedance mismatch, the stronger the reflection.",
BODY))
impedance_data = [
[Paragraph("<b>Tissue</b>", TABLE_HDR),
Paragraph("<b>Relative Impedance</b>", TABLE_HDR),
Paragraph("<b>US Appearance</b>", TABLE_HDR),
Paragraph("<b>Notes</b>", TABLE_HDR)],
["Air / Gas", "Very Low", "Black + blocks view", "No coupling - needs gel"],
["Fat", "Low", "Dark grey", "Poor reflector"],
["Soft tissue", "Intermediate", "Various grey shades", "Most organs"],
["Blood", "Low", "Anechoic (black)", "Simple fluid"],
["Bone / Calcium", "Very High", "Bright white + shadow", "Total reflection"],
["Diaphragm", "High", "Hyperechoic bright line", "Strong reflector"],
]
it = Table(impedance_data, colWidths=[(W-2*MARGIN)*x for x in [0.2, 0.2, 0.28, 0.32]])
it.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), TEAL),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("ROWBACKGROUNDS",(0,1), (-1,-1), [GRAY_LT, WHITE]),
("GRID", (0,0), (-1,-1), 0.4, GRAY_MID),
("FONTSIZE", (0,0), (-1,-1), 8.5),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
story.append(it)
story.append(PageBreak())
# ═══════════════════════════════════════════════════════════════════════════
# SECTION 2: PROBE SELECTION
# ═══════════════════════════════════════════════════════════════════════════
story.append(sp(10))
story.append(section_header("SECTION 2 | PROBE (TRANSDUCER) SELECTION", TEAL))
story.append(sp(8))
story.append(Paragraph(
"Transducers differ in frequency, crystal arrangement, and footprint shape. "
"Choosing the correct probe is the single most important step before any scan.",
BODY))
story.append(Paragraph("2.1 The Three Main Probe Types", H2))
# Probe type cards (3-column table)
def probe_card(name, freq, shape, field, best_for_list, color):
items = [
Paragraph(f"<b>{name}</b>", style("pc_name","Normal",fontSize=11,textColor=WHITE,alignment=TA_CENTER)),
Spacer(1,3),
Paragraph(f"<b>Frequency:</b> {freq}", style("pc_f","Normal",fontSize=8.5,textColor=WHITE,leading=12)),
Paragraph(f"<b>Crystal arrangement:</b> {shape}", style("pc_s","Normal",fontSize=8.5,textColor=WHITE,leading=12)),
Paragraph(f"<b>Field shape:</b> {field}", style("pc_fld","Normal",fontSize=8.5,textColor=WHITE,leading=12)),
Spacer(1,5),
Paragraph("<b>Best for:</b>", style("pc_bf","Normal",fontSize=8.5,textColor=WHITE,leading=12)),
] + [
Paragraph(f" + {item}", style("pc_bi","Normal",fontSize=8.2,textColor=colors.HexColor("#DDEEFF"),leading=12))
for item in best_for_list
]
inner = Table([[i] for i in items], colWidths=[(W-2*MARGIN)/3 - 8])
inner.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), color),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 8),
("RIGHTPADDING", (0,0), (-1,-1), 6),
("BOX", (0,0), (-1,-1), 1, WHITE),
]))
return inner
probe_cards = Table([[
probe_card("LINEAR PROBE",
"5-15 MHz",
"Crystals in straight line",
"Rectangular",
["Peripheral veins & arteries",
"Thyroid & parathyroid",
"Tendons & muscles",
"Breast & lymph nodes",
"Deep vein thrombosis (DVT)",
"Nerve blocks",
"Skin & soft tissue abscess"],
NAVY),
probe_card("CURVILINEAR PROBE",
"2-5 MHz",
"Curved linear array",
"Wide fan (arc)",
["Abdominal organs (liver, kidneys)",
"Spleen, gallbladder",
"Aorta & IVC",
"Bladder & pelvis",
"OB/GYN scanning",
"FAST exam",
"Pleural effusion"],
TEAL),
probe_card("PHASED ARRAY PROBE",
"1-5 MHz",
"Small central array",
"Pie / sector",
["Cardiac (fits between ribs)",
"Lung & pleura",
"Pericardial effusion",
"IVC assessment",
"Sub-xiphoid views",
"Focused cardiac US",
"Critically ill patients"],
colors.HexColor("#1A5276")),
]], colWidths=[(W-2*MARGIN)/3]*3)
probe_cards.setStyle(TableStyle([
("TOPPADDING", (0,0), (-1,-1), 0),
("BOTTOMPADDING", (0,0), (-1,-1), 0),
("LEFTPADDING", (0,0), (-1,-1), 2),
("RIGHTPADDING", (0,0), (-1,-1), 2),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
story.append(probe_cards)
story.append(sp(10))
story.append(Paragraph("2.2 Probe Selection Quick-Reference", H2))
qr_data = [
[Paragraph("<b>Clinical Scenario</b>", TABLE_HDR),
Paragraph("<b>Probe</b>", TABLE_HDR),
Paragraph("<b>Frequency</b>", TABLE_HDR),
Paragraph("<b>Reason</b>", TABLE_HDR)],
["DVT assessment (leg veins)",
"Linear", "7-15 MHz", "Superficial veins need high resolution"],
["RUQ / gallbladder",
"Curvilinear", "3-5 MHz", "Moderate depth; wide field for liver"],
["Kidney (both sides)",
"Curvilinear", "2-5 MHz", "Deep organ; wide beam for parenchyma"],
["Focused cardiac (ECHO)",
"Phased array", "1-5 MHz", "Small footprint fits intercostal spaces"],
["Aorta (AAA screen)",
"Curvilinear", "2-5 MHz", "Deep retroperitoneal structure"],
["Thyroid / parathyroid",
"Linear", "10-15 MHz", "Very superficial; fine detail needed"],
["Pleural effusion",
"Curvilinear / Phased", "2-5 MHz", "Need depth + wide field"],
["Lung / pneumothorax",
"Linear / Phased", "5-15 MHz", "Pleural line is superficial"],
["IVC collapsibility",
"Phased array", "1-5 MHz", "Sub-xiphoid window, deep vessel"],
["Soft tissue abscess",
"Linear", "7-15 MHz", "Superficial; need wall detail"],
["Bladder (pelvic)",
"Curvilinear", "3-5 MHz", "Pelvic depth; wide beam"],
["Central line guidance (IJV/SC)",
"Linear", "7-15 MHz", "Superficial veins, real-time needle tip"],
]
qrt = Table(qr_data, colWidths=[(W-2*MARGIN)*x for x in [0.35, 0.18, 0.16, 0.31]])
qrt.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), NAVY),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("ROWBACKGROUNDS",(0,1), (-1,-1), [GRAY_LT, WHITE]),
("GRID", (0,0), (-1,-1), 0.4, GRAY_MID),
("FONTSIZE", (0,1), (-1,-1), 8),
("FONTSIZE", (0,0), (-1,0), 8.5),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
story.append(qrt)
story.append(PageBreak())
# ═══════════════════════════════════════════════════════════════════════════
# SECTION 3: MACHINE CONTROLS
# ═══════════════════════════════════════════════════════════════════════════
story.append(sp(10))
story.append(section_header("SECTION 3 | MACHINE CONTROLS (KNOBOLOGY)", colors.HexColor("#1A7A4A")))
story.append(sp(8))
story.append(Paragraph(
"Mastering the basic machine controls lets you optimise image quality for any patient. "
"The most commonly needed adjustments are gain, depth, and focus.",
BODY))
controls_data = [
[Paragraph("<b>Control</b>", TABLE_HDR),
Paragraph("<b>What It Does</b>", TABLE_HDR),
Paragraph("<b>How to Set It</b>", TABLE_HDR),
Paragraph("<b>Common Error</b>", TABLE_HDR)],
[Paragraph("<b>GAIN</b>", style("ctrl","Normal",fontSize=9,textColor=NAVY,leading=12)),
"Amplifies ALL returning echo signals equally - makes image brighter or darker",
"Set so simple fluid (urine/blood) = black; normal tissue = mid-grey",
"Too high: image is washed out white. Too low: structures disappear into black"],
[Paragraph("<b>TIME GAIN COMP (TGC)</b>", style("ctrl","Normal",fontSize=9,textColor=NAVY,leading=12)),
"Adjusts gain at specific depths - compensates for sound attenuation deeper in tissue",
"Slide individual depth sliders so brightness is uniform top-to-bottom",
"Ignoring TGC makes deep structures look darker than superficial ones"],
[Paragraph("<b>DEPTH</b>", style("ctrl","Normal",fontSize=9,textColor=NAVY,leading=12)),
"Controls how deep the image extends on screen",
"Set so the target structure occupies the middle 1/3 of the screen",
"Too deep: small structures appear tiny. Too shallow: you miss posterior structures"],
[Paragraph("<b>FOCUS</b>", style("ctrl","Normal",fontSize=9,textColor=NAVY,leading=12)),
"Narrows beam at chosen depth for best lateral resolution at that zone",
"Place focus at the depth of the structure you are examining",
"Focus left at default (mid-screen) when target is at a different depth"],
[Paragraph("<b>FREQUENCY</b>", style("ctrl","Normal",fontSize=9,textColor=NAVY,leading=12)),
"Many probes let you adjust frequency within a range (eg 5-12 MHz)",
"Start high for resolution; decrease if penetration is insufficient",
"Leaving frequency too high for an obese patient causes signal dropout"],
[Paragraph("<b>FREEZE / CINE</b>", style("ctrl","Normal",fontSize=9,textColor=NAVY,leading=12)),
"Freezes the live image; cine loop lets you scroll back through recent frames",
"Freeze at the best image frame; scroll cine to find optimal moment",
"Taking measurements on a live (non-frozen) image introduces motion blur"],
]
ct2 = Table(controls_data, colWidths=[(W-2*MARGIN)*x for x in [0.18, 0.27, 0.27, 0.28]])
ct2.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), NAVY),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("ROWBACKGROUNDS",(0,1), (-1,-1), [GREEN_LT, WHITE]),
("GRID", (0,0), (-1,-1), 0.4, GRAY_MID),
("FONTSIZE", (0,1), (-1,-1), 8),
("FONTSIZE", (0,0), (-1,0), 8.5),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
story.append(ct2)
story.append(sp(10))
# ── Modes ──
story.append(Paragraph("3.2 Ultrasound Imaging Modes", H2))
modes_data = [
[Paragraph("<b>Mode</b>", TABLE_HDR),
Paragraph("<b>Full Name</b>", TABLE_HDR),
Paragraph("<b>Description</b>", TABLE_HDR),
Paragraph("<b>Clinical Uses</b>", TABLE_HDR)],
["B-mode", "Brightness Mode",
"Standard 2D greyscale image. Amplitude of echo = brightness of dot on screen",
"All routine scanning; anatomy; organ assessment"],
["M-mode", "Motion Mode",
"Single ultrasound line plotted vs. time. Shows movement of structures over time",
"Fetal heart rate; cardiac wall motion; pleural sliding; valve leaflets"],
["Color Doppler", "Color Flow Doppler",
"Detects moving blood; encodes direction as color (red = toward probe, blue = away)",
"Valve regurgitation; vessel stenosis; DVT; vascular anatomy"],
["Power Doppler", "Power Angiography",
"Detects velocity of flow; no directional info. More sensitive for slow flow",
"Organ perfusion; low-flow vessels; testes (torsion); inflammatory arthritis"],
["PW Doppler", "Pulsed Wave Doppler",
"Samples velocity at specific depth ('sample volume'). Shows waveform over time",
"Quantify stenosis; cardiac output; renal artery Doppler; hepatic veins"],
["CW Doppler", "Continuous Wave",
"Samples all velocities along entire beam line. Measures very high velocities",
"Severe aortic stenosis; high-pressure TR jets; measuring high velocities"],
]
mt = Table(modes_data, colWidths=[(W-2*MARGIN)*x for x in [0.13, 0.17, 0.37, 0.33]])
mt.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), TEAL),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("ROWBACKGROUNDS",(0,1), (-1,-1), [LIGHT_BLU, WHITE]),
("GRID", (0,0), (-1,-1), 0.4, GRAY_MID),
("FONTSIZE", (0,1), (-1,-1), 8),
("FONTSIZE", (0,0), (-1,0), 8.5),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "TOP"),
("FONTNAME", (0,1), (0,-1), "Helvetica-Bold"),
("TEXTCOLOR", (0,1), (0,-1), NAVY),
]))
story.append(mt)
story.append(PageBreak())
# ═══════════════════════════════════════════════════════════════════════════
# SECTION 4: ECHOGENICITY & IMAGE INTERPRETATION
# ═══════════════════════════════════════════════════════════════════════════
story.append(sp(10))
story.append(section_header("SECTION 4 | ECHOGENICITY & IMAGE INTERPRETATION", AMBER))
story.append(sp(8))
story.append(Paragraph("4.1 The Echogenicity Scale", H2))
story.append(Paragraph(
"Echogenicity describes how bright or dark a structure appears on the ultrasound image. "
"This depends on how strongly the tissue reflects ultrasound waves back to the transducer.",
BODY))
echo_data = [
[Paragraph("<b>Term</b>", TABLE_HDR),
Paragraph("<b>Appearance</b>", TABLE_HDR),
Paragraph("<b>What It Means</b>", TABLE_HDR),
Paragraph("<b>Examples</b>", TABLE_HDR)],
[Paragraph("<b>ANECHOIC</b>", style("ae","Normal",fontSize=9,textColor=colors.HexColor("#003399"),leading=12)),
Paragraph("<font color='#333333'>Completely BLACK</font>", BODY_SM),
"No reflection - sound passes straight through",
"Urine, bile, blood, simple cysts, CSF"],
[Paragraph("<b>HYPOECHOIC</b>", style("hpo","Normal",fontSize=9,textColor=colors.HexColor("#336699"),leading=12)),
Paragraph("<font color='#444444'>Dark GREY (darker than surrounding)</font>", BODY_SM),
"Fewer/weaker reflections than adjacent tissue",
"Renal cortex vs liver; lymph nodes; solid tumors"],
[Paragraph("<b>ISOECHOIC</b>", style("iso","Normal",fontSize=9,textColor=colors.HexColor("#555555"),leading=12)),
Paragraph("Same GREY as surroundings", BODY_SM),
"Same reflective properties as adjacent tissue",
"Normal kidney cortex = medulla in CKD"],
[Paragraph("<b>HYPERECHOIC</b>", style("hpe","Normal",fontSize=9,textColor=colors.HexColor("#CC7700"),leading=12)),
Paragraph("<font color='#AA5500'>Bright WHITE</font>", BODY_SM),
"Strong reflection of sound waves",
"Bone cortex, diaphragm, pericardium, calculi, fat"],
[Paragraph("<b>ECHOGENIC</b>", style("eg","Normal",fontSize=9,textColor=TEAL,leading=12)),
Paragraph("<font color='#007C9E'>Bright (relative)</font>", BODY_SM),
"General term for a reflective structure",
"Often used loosely for 'bright' findings"],
]
et = Table(echo_data, colWidths=[(W-2*MARGIN)*x for x in [0.18, 0.22, 0.30, 0.30]])
et.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), NAVY),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("ROWBACKGROUNDS",(0,1), (-1,-1), [GRAY_LT, WHITE]),
("GRID", (0,0), (-1,-1), 0.4, GRAY_MID),
("FONTSIZE", (0,1), (-1,-1), 8),
("FONTSIZE", (0,0), (-1,0), 8.5),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
story.append(et)
story.append(sp(10))
# ═══════════════════════════════════════════════════════════════════════════
# SECTION 5: ARTIFACTS
# ═══════════════════════════════════════════════════════════════════════════
story.append(Paragraph("4.2 Common Ultrasound Artifacts", H2))
story.append(Paragraph(
"In ultrasound, <b>artifacts</b> are image features that do not correspond to real anatomic "
"structures. Recognizing them prevents misdiagnosis - and several artifacts provide "
"useful diagnostic information.",
BODY))
# Shadowing/Enhancement image
story.append(Paragraph("<b>Acoustic Shadowing vs. Posterior Enhancement</b>", H3))
sh_imgs = img("shadowing_enhancement.png", 150,
"Figure 2. (A) Rib casts an acoustic shadow - the area below appears black. This is NOT absence of "
"tissue; it means sound cannot penetrate beyond the dense bone. (B) Posterior acoustic enhancement "
"behind the fluid-filled carotid artery and internal jugular vein - the bright 'tail' indicates a "
"low-impedance fluid-filled structure above. (Murray & Nadel's Textbook of Respiratory Medicine)")
for item in sh_imgs:
story.append(item)
story.append(sp(6))
# Reverberation image
story.append(Paragraph("<b>Reverberation Artifact (A-lines)</b>", H3))
rev_imgs = img("reverberation.png", 150,
"Figure 3. Reverberation artifact generating A-lines in lung ultrasound. Sound bounces between "
"the transducer face and the pleural surface, producing repeating equidistant horizontal lines (A-lines). "
"These indicate either normal aerated lung or pneumothorax. (Murray & Nadel's Textbook of Respiratory Medicine)")
for item in rev_imgs:
story.append(item)
story.append(sp(6))
# Artifact summary table
art_data = [
[Paragraph("<b>Artifact</b>", TABLE_HDR),
Paragraph("<b>Appearance</b>", TABLE_HDR),
Paragraph("<b>Cause</b>", TABLE_HDR),
Paragraph("<b>Clinical Meaning</b>", TABLE_HDR)],
["Acoustic Shadow",
"Black band deep to bright structure",
"Dense structure (bone, stone) reflects/absorbs all sound",
"Gallstones, renal calculi, bones, calcification"],
["Posterior Enhancement",
"Bright 'tail' deep to dark (fluid) structure",
"Fluid attenuates sound less than surrounding tissue",
"Distinguishes simple cyst from solid mass"],
["Reverberation / A-lines",
"Repeating horizontal lines at equal intervals",
"Sound bouncing between two strong reflectors",
"Aerated lung (normal) or pneumothorax"],
["B-lines",
"Vertical laser-like lines from pleura to screen bottom",
"Near-pleural fluid-filled interlobular septa",
"Pulmonary edema, pneumonia, interstitial lung disease"],
["Mirror Artifact",
"Structure duplicated on both sides of a reflector",
"Sound takes alternate path via highly reflective surface",
"Liver/spleen 'mirrored' above diaphragm - normal"],
["Twinkle Artifact",
"Rapid color flickering behind echogenic focus",
"Rough surface generates rapid frequency shifts",
"Urinary calculi on color Doppler - aids stone detection"],
]
at = Table(art_data, colWidths=[(W-2*MARGIN)*x for x in [0.20, 0.22, 0.28, 0.30]])
at.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), NAVY),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("ROWBACKGROUNDS",(0,1), (-1,-1), [AMBER_LT, WHITE]),
("GRID", (0,0), (-1,-1), 0.4, GRAY_MID),
("FONTSIZE", (0,1), (-1,-1), 8),
("FONTSIZE", (0,0), (-1,0), 8.5),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "TOP"),
("FONTNAME", (0,1), (0,-1), "Helvetica-Bold"),
("TEXTCOLOR", (0,1), (0,-1), colors.HexColor("#8B4500")),
]))
story.append(at)
story.append(PageBreak())
# ═══════════════════════════════════════════════════════════════════════════
# SECTION 5: ORIENTATION & PROBE MOVEMENTS
# ═══════════════════════════════════════════════════════════════════════════
story.append(sp(10))
story.append(section_header("SECTION 5 | ORIENTATION, PROBE MOVEMENTS & SAFETY", colors.HexColor("#4A235A")))
story.append(sp(8))
story.append(Paragraph("5.1 Image Orientation", H2))
story.append(Paragraph(
"All ultrasound images have a consistent orientation convention. Understanding it prevents "
"left-right errors and misidentification of structures.",
BODY))
orient_data = [
[Paragraph("<b>Screen Position</b>", TABLE_HDR),
Paragraph("<b>Corresponds To</b>", TABLE_HDR),
Paragraph("<b>Convention</b>", TABLE_HDR)],
["TOP of screen", "Closest to transducer (most superficial)",
"Always - near-field structures appear at top"],
["BOTTOM of screen", "Farthest from transducer (deepest)",
"Always - far-field structures at bottom"],
["LEFT of screen", "Direction the probe marker points",
"Abdominal: marker toward patient's HEAD = cephalad on left"],
["RIGHT of screen", "Opposite side from probe marker",
"Abdominal: caudal / inferior structures on right"],
["Cardiac (ECHO)", "EXCEPTION: screen indicator is on RIGHT",
"Cardiac convention is opposite to abdominal"],
]
ot = Table(orient_data, colWidths=[(W-2*MARGIN)*x for x in [0.22, 0.38, 0.40]])
ot.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), colors.HexColor("#4A235A")),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("ROWBACKGROUNDS",(0,1), (-1,-1), [colors.HexColor("#F3E8FA"), WHITE]),
("GRID", (0,0), (-1,-1), 0.4, GRAY_MID),
("FONTSIZE", (0,1), (-1,-1), 8),
("FONTSIZE", (0,0), (-1,0), 8.5),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "TOP"),
("FONTNAME", (0,1), (0,-1), "Helvetica-Bold"),
("TEXTCOLOR", (0,1), (0,-1), colors.HexColor("#4A235A")),
]))
story.append(ot)
story.append(sp(8))
story.append(Paragraph("5.2 Probe Movements", H2))
move_data = [
[Paragraph("<b>Movement</b>", TABLE_HDR),
Paragraph("<b>Description</b>", TABLE_HDR),
Paragraph("<b>Purpose</b>", TABLE_HDR)],
["SLIDING", "Translating the probe across the skin surface",
"Survey a larger anatomical area"],
["FANNING / TILTING", "Angling the probe face side-to-side keeping footprint still",
"Open up view behind structures; track vessels"],
["ROCKING", "Tilting probe along its long axis (within the imaging plane)",
"Extend the image cephalad or caudad without moving footprint"],
["ROTATING", "Spinning the probe on its central axis",
"Switch between longitudinal and transverse (short axis) views"],
["COMPRESSION", "Pressing down firmly to displace gas or assess compressibility",
"Improve abdominal views; DVT assessment (non-compressible vein = clot)"],
]
pm = Table(move_data, colWidths=[(W-2*MARGIN)*x for x in [0.22, 0.38, 0.40]])
pm.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), NAVY),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("ROWBACKGROUNDS",(0,1), (-1,-1), [GRAY_LT, WHITE]),
("GRID", (0,0), (-1,-1), 0.4, GRAY_MID),
("FONTSIZE", (0,1), (-1,-1), 8),
("FONTSIZE", (0,0), (-1,0), 8.5),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "TOP"),
("FONTNAME", (0,1), (0,-1), "Helvetica-Bold"),
("TEXTCOLOR", (0,1), (0,-1), NAVY),
]))
story.append(pm)
story.append(sp(8))
story.append(Paragraph("5.3 Safety - ALARA Principle", H2))
story.append(colored_box(
" ALARA = As Low As Reasonably Achievable",
[
bullet("<b>Only scan when clinically indicated</b> - do not use ultrasound out of curiosity"),
bullet("<b>Limit scan duration</b> - the shorter the scan, the lower the total energy deposition"),
bullet("<b>Minimize Doppler modes</b> over sensitive tissues: early embryo (1st trimester), "
"fetal eye, germinal matrix, neural tissue, mucosal surfaces"),
bullet("<b>Surface probes:</b> clean with low-level disinfection (spray/wipe) after each use"),
bullet("<b>Endocavitary probes</b> (transvaginal, transrectal): require <b>high-level disinfection</b> "
"and sterile cover"),
bullet("<b>Procedural guidance:</b> use sterile probe cover and sterile gel"),
],
bg=GREEN_LT, title_bg=GREEN
))
story.append(sp(10))
# ── Cardiac views image ──
story.append(section_header("SECTION 6 | FOCUSED CARDIAC ULTRASOUND - STANDARD VIEWS", TEAL))
story.append(sp(8))
story.append(Paragraph(
"Once probe selection and orientation are mastered, focused cardiac ultrasound (FoCUS) applies "
"all these principles. Four standard windows give a comprehensive cardiac assessment.",
BODY))
card_imgs = img("cardiac_views.png", 155,
"Figure 4. Standard FoCUS views. Left: transducer positions on the chest (green arrows = probe "
"marker direction). (A) Parasternal long axis - LV, mitral valve (MV), aortic valve (AV), aorta (Ao), LA. "
"(B) Parasternal short axis - circular LV cross-section with papillary muscles (arrow). "
"(C) Apical 4-chamber - all 4 chambers, tricuspid valve (TV) and MV. "
"(D) Subcostal 4-chamber - through liver window. (Comprehensive Clinical Nephrology, 7th Ed.)")
for item in card_imgs:
story.append(item)
story.append(sp(8))
cardiac_data = [
[Paragraph("<b>View</b>", TABLE_HDR),
Paragraph("<b>Position</b>", TABLE_HDR),
Paragraph("<b>Probe Marker</b>", TABLE_HDR),
Paragraph("<b>Structures Seen</b>", TABLE_HDR)],
["Parasternal Long Axis (PLAX)",
"Left sternal border, 3rd-4th ICS",
"Toward patient's RIGHT shoulder (10-11 o'clock)",
"LV, RV, MV, AV, Aorta, LA, descending aorta"],
["Parasternal Short Axis (PSAX)",
"Same position as PLAX, rotate 90 degrees",
"Toward patient's LEFT shoulder (2 o'clock)",
"Cross-section LV + papillary muscles; RV anteriorly"],
["Apical 4-Chamber",
"Cardiac apex (4th-5th ICS, lateral)",
"Toward patient's LEFT (2-3 o'clock)",
"All 4 chambers; MV and TV; assess AV regurgitation"],
["Subcostal 4-Chamber",
"Sub-xiphoid, probe flat, through liver",
"Toward patient's LEFT side",
"All 4 chambers; pericardial effusion; IVC"],
]
cdt = Table(cardiac_data, colWidths=[(W-2*MARGIN)*x for x in [0.23, 0.25, 0.25, 0.27]])
cdt.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), TEAL),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("ROWBACKGROUNDS",(0,1), (-1,-1), [LIGHT_BLU, WHITE]),
("GRID", (0,0), (-1,-1), 0.4, GRAY_MID),
("FONTSIZE", (0,1), (-1,-1), 8),
("FONTSIZE", (0,0), (-1,0), 8.5),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
story.append(cdt)
story.append(PageBreak())
# ═══════════════════════════════════════════════════════════════════════════
# FINAL PAGE: QUICK REFERENCE CARD
# ═══════════════════════════════════════════════════════════════════════════
story.append(sp(10))
story.append(section_header("QUICK REFERENCE CARD | SCAN CHECKLIST & KEY FORMULAS", NAVY))
story.append(sp(8))
story.append(Paragraph("Before Every Scan - Step-by-Step Checklist", H2))
checklist = [
("1. SELECT PROBE", "Linear (superficial) / Curvilinear (abdomen) / Phased array (cardiac/lung)"),
("2. APPLY GEL", "Generous coupling gel - removes air between probe and skin"),
("3. SET DEPTH", "Target structure in middle third of screen"),
("4. ADJUST GAIN", "Simple fluid = black; normal parenchyma = mid-grey"),
("5. SET TGC", "Uniform brightness top to bottom"),
("6. PLACE FOCUS", "At depth of structure of interest"),
("7. ORIENT IMAGE", "Find probe marker; confirm what is on LEFT vs RIGHT of screen"),
("8. IDENTIFY LANDMARKS", "Name key structures before interpreting pathology"),
("9. NOTE ECHOGENICITY", "Anechoic / hypo / iso / hyperechoic relative to reference tissue"),
("10. SPOT ARTIFACTS", "Shadow, enhancement, reverberation, B-lines, mirror"),
("11. APPLY DOPPLER", "Add color/PW Doppler only when flow assessment is needed"),
("12. MEASURE & FREEZE", "Freeze image; measure outer wall to outer wall"),
]
cl_data = [[Paragraph(f"<b>{n}</b>", style("cln","Normal",fontSize=8.5,textColor=NAVY,leading=12)),
Paragraph(d, BODY_SM)] for n, d in checklist]
clt = Table(cl_data, colWidths=[(W-2*MARGIN)*0.28, (W-2*MARGIN)*0.72])
clt.setStyle(TableStyle([
("ROWBACKGROUNDS", (0,0), (-1,-1), [GRAY_LT, WHITE]),
("GRID", (0,0), (-1,-1), 0.4, GRAY_MID),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("LINEABOVE", (0,0), (-1,0), 1, NAVY),
("LINEBELOW", (0,-1),(-1,-1), 1, NAVY),
]))
story.append(clt)
story.append(sp(10))
# Key formulas / mnemonics
story.append(colored_box(
" Key Formulas & Mnemonics",
[
Paragraph("<b>Distance formula:</b> d = (c x t) / 2 [c = 1540 m/s in soft tissue; t = round-trip time]", BOX_BODY),
Paragraph("<b>Resolution rule:</b> Higher frequency = better resolution BUT less penetration", BOX_BODY),
Paragraph("<b>Probe mnemonic - 'LAP':</b> <b>L</b>inear (superficial) | <b>A</b>bdominal curvilinear (deep) | <b>P</b>hased array (cardiac)", BOX_BODY),
Paragraph("<b>Echogenicity mnemonic - 'AHIB':</b> <b>A</b>nechoic (black) | <b>H</b>ypoechoic (dark grey) | <b>I</b>soechoic (same) | <b>H</b>yperechoic (bright) | <b>B</b>ig shadows from bones", BOX_BODY),
Paragraph("<b>ALARA:</b> As Low As Reasonably Achievable - always minimise Doppler over sensitive tissue", BOX_BODY),
Paragraph("<b>Artifact rule:</b> Shadow = dense reflector above | Enhancement = fluid above | A-lines = air/lung above", BOX_BODY),
],
bg=LIGHT_BLU, title_bg=NAVY
))
story.append(sp(8))
story.append(colored_box(
" Common Definitions at a Glance",
[
Paragraph(
"<b>Gain</b> - brightness | <b>TGC</b> - depth-specific brightness | <b>Depth</b> - how deep the image extends | "
"<b>Focus</b> - zone of best lateral resolution | <b>Near field</b> - transducer to focus zone | "
"<b>Far field</b> - below focus to screen bottom | <b>Window</b> - acoustic access point on body surface | "
"<b>FAST exam</b> - Focused Assessment with Sonography in Trauma (hemoperitoneum, hemopericardium, hemothorax) | "
"<b>POCUS</b> - Point-Of-Care UltraSound | <b>FoCUS</b> - Focused Cardiac UltraSound",
BOX_BODY),
],
bg=AMBER_LT, title_bg=AMBER
))
story.append(sp(8))
# Sources footer box
story.append(colored_box(
" Sources",
[
Paragraph("1. Murray & Nadel's Textbook of Respiratory Medicine (Chapter 23 - Point-of-Care Ultrasound)", BOX_BODY),
Paragraph("2. Rosen's Emergency Medicine: Concepts and Clinical Practice (Chapter e3 - Emergency Ultrasound)", BOX_BODY),
Paragraph("3. Comprehensive Clinical Nephrology, 7th Edition (Chapter 5 - Focused Cardiac and Renal Ultrasound)", BOX_BODY),
Paragraph("4. Pfenninger & Fowler's Procedures for Primary Care, 3rd Edition (Chapter 121 - Prostate Ultrasound)", BOX_BODY),
],
bg=GRAY_LT, title_bg=colors.HexColor("#444444")
))
# ═══════════════════════════════════════════════════════════════════════════
# BUILD
# ═══════════════════════════════════════════════════════════════════════════
doc.build(story,
onFirstPage=on_first_page,
onLaterPages=on_page)
print(f"PDF created: {OUT_FILE}")
print(f"File size: {os.path.getsize(OUT_FILE):,} bytes")
cd /home/daytona/workspace/usg-study-guide && python3 generate_pdf.py 2>&1
~/usg-study-guide/generate_pdf.py
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/usg-study-guide/generate_pdf.py'
cd /home/daytona/workspace/usg-study-guide && python3 generate_pdf.py 2>&1
USG Physics ProbeSelection StudyGuide
PDF Document · PDF
| Section | Content |
|---|---|
| 1. Physics Fundamentals | Pulse-echo principle, frequency vs wavelength, axial vs lateral resolution (with diagram), acoustic impedance table |
| 2. Probe Selection | 3 color-coded probe cards (Linear / Curvilinear / Phased array) + 12-row clinical quick-reference table |
| 3. Machine Controls | Gain, TGC, depth, focus, freeze - with how-to-set and common errors for each |
| 4. Echogenicity & Artifacts | Full echogenicity scale + artifact table with acoustic shadowing, posterior enhancement, A-lines (with 2 real USG images) |
| 5. Orientation & Safety | Probe marker conventions, 5 probe movements, ALARA principle |
| 6. Cardiac Views | FoCUS 4-window reference with annotated cardiac image + quick-reference table |
| Quick Reference Card | 12-step scan checklist, key formulas, LAP/AHIB/ALARA mnemonics, glossary box |