Viva answers to all these questions and cross questions that could be asked
Create a study PDF with key figures and conversion tables for radiation topics
ls -la /home/daytona/workspace/ 2>/dev/null || echo "workspace empty or not foun…
mkdir -p /home/daytona/workspace/radiation-study && python3 -c "import reportlab…
~/radiation-study/build_pdf.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import cm, mm
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, KeepTogether, PageBreak
)
from reportlab.platypus.flowables import Flowable
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT
from reportlab.graphics.shapes import Drawing, Rect, String, Line, Circle, Polygon
from reportlab.graphics.charts.barcharts import VerticalBarChart
from reportlab.graphics import renderPDF
import math
# ── Colour palette ──────────────────────────────────────────────────────────
NAVY = colors.HexColor("#1a2a4a")
TEAL = colors.HexColor("#0d7377")
GOLD = colors.HexColor("#f5a623")
LIGHT_BG = colors.HexColor("#eef4fb")
LIGHT_TL = colors.HexColor("#e0f5f5")
RED_ACC = colors.HexColor("#c0392b")
GREEN_ACC = colors.HexColor("#27ae60")
ORANGE = colors.HexColor("#e67e22")
PURPLE = colors.HexColor("#8e44ad")
WHITE = colors.white
GREY_LT = colors.HexColor("#f2f2f2")
GREY_MID = colors.HexColor("#bdc3c7")
W, H = A4
# ── Styles ───────────────────────────────────────────────────────────────────
base = getSampleStyleSheet()
def S(name, **kw):
s = ParagraphStyle(name, **kw)
return s
TITLE_S = S("title_s", fontName="Helvetica-Bold", fontSize=24, textColor=WHITE, alignment=TA_CENTER, spaceAfter=4)
SUB_S = S("sub_s", fontName="Helvetica", fontSize=11, textColor=LIGHT_BG, alignment=TA_CENTER, spaceAfter=2)
H1_S = S("h1_s", fontName="Helvetica-Bold", fontSize=14, textColor=NAVY, spaceBefore=12, spaceAfter=4)
H2_S = S("h2_s", fontName="Helvetica-Bold", fontSize=11, textColor=WHITE, spaceBefore=2, spaceAfter=2)
BODY_S = S("body_s", fontName="Helvetica", fontSize=9, textColor=colors.black, leading=14, spaceAfter=3)
SMALL_S = S("small_s", fontName="Helvetica", fontSize=8, textColor=colors.HexColor("#444444"), leading=12)
BOLD_S = S("bold_s", fontName="Helvetica-Bold", fontSize=9, textColor=NAVY)
NOTE_S = S("note_s", fontName="Helvetica-Oblique",fontSize=8, textColor=RED_ACC, leading=12)
FOOT_S = S("foot_s", fontName="Helvetica", fontSize=7, textColor=GREY_MID, alignment=TA_CENTER)
CELL_HDR = S("cell_hdr", fontName="Helvetica-Bold", fontSize=8, textColor=WHITE, alignment=TA_CENTER, leading=10)
CELL_S = S("cell_s", fontName="Helvetica", fontSize=8, textColor=colors.black, alignment=TA_CENTER, leading=10)
CELL_L = S("cell_l", fontName="Helvetica", fontSize=8, textColor=colors.black, alignment=TA_LEFT, leading=10)
CELL_BL = S("cell_bl", fontName="Helvetica-Bold", fontSize=8, textColor=NAVY, alignment=TA_LEFT, leading=10)
# ── Helpers ───────────────────────────────────────────────────────────────────
def hdr_para(text, style=H2_S, bg=NAVY):
"""Coloured header bar with white text via single-cell table."""
t = Table([[Paragraph(text, style)]], colWidths=[W - 4*cm])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), bg),
("LEFTPADDING", (0,0), (-1,-1), 8),
("RIGHTPADDING", (0,0), (-1,-1), 8),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING",(0,0), (-1,-1), 5),
("ROUNDEDCORNERS", (0,0), (-1,-1), [4,4,4,4]),
]))
return t
def box_table(data, col_widths, header_rows=1,
hdr_bg=NAVY, alt_bg=LIGHT_BG, border=TEAL):
style = [
("BACKGROUND", (0,0), (-1, header_rows-1), hdr_bg),
("TEXTCOLOR", (0,0), (-1, header_rows-1), WHITE),
("FONTNAME", (0,0), (-1, header_rows-1), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 8),
("ALIGN", (0,0), (-1,-1), "CENTER"),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("GRID", (0,0), (-1,-1), 0.4, border),
("ROWBACKGROUNDS",(0, header_rows), (-1,-1), [WHITE, alt_bg]),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING",(0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
]
return Table(data, colWidths=col_widths, style=TableStyle(style), repeatRows=header_rows)
def bullet(text, indent=0.3):
return Paragraph(f"<bullet>•</bullet> {text}",
ParagraphStyle("bul", fontName="Helvetica", fontSize=8.5,
textColor=colors.black, leading=13,
leftIndent=indent*cm, spaceAfter=2))
def red_bullet(text):
return Paragraph(f"<font color='#c0392b'><b>!</b></font> {text}",
ParagraphStyle("rbul", fontName="Helvetica", fontSize=8.5,
textColor=colors.black, leading=13,
leftIndent=0.4*cm, spaceAfter=2))
def sp(n=1): return Spacer(1, n*0.35*cm)
# ── Custom Flowables ──────────────────────────────────────────────────────────
class TitleBlock(Flowable):
"""Full-width gradient-style title block."""
def __init__(self, w=W-4*cm, h=3.8*cm):
super().__init__()
self.W = w; self.H = h
def wrap(self, *args): return self.W, self.H
def draw(self):
c = self.canv
# Background gradient (simulate with rects)
steps = 30
for i in range(steps):
r = NAVY.red + (TEAL.red - NAVY.red) * i / steps
g = NAVY.green + (TEAL.green - NAVY.green) * i / steps
b = NAVY.blue + (TEAL.blue - NAVY.blue) * i / steps
c.setFillColorRGB(r, g, b)
c.rect(self.W*i/steps, 0, self.W/steps+1, self.H, fill=1, stroke=0)
# Gold accent bar
c.setFillColor(GOLD)
c.rect(0, self.H-0.18*cm, self.W, 0.18*cm, fill=1, stroke=0)
c.rect(0, 0, self.W, 0.12*cm, fill=1, stroke=0)
# Title text
c.setFillColor(WHITE)
c.setFont("Helvetica-Bold", 22)
c.drawCentredString(self.W/2, self.H - 1.2*cm, "ORAL RADIOLOGY")
c.setFont("Helvetica-Bold", 17)
c.drawCentredString(self.W/2, self.H - 2.0*cm, "Radiation Study Guide")
c.setFont("Helvetica", 10)
c.setFillColor(LIGHT_BG)
c.drawCentredString(self.W/2, self.H - 2.65*cm,
"Key Figures · Conversion Tables · Clinical Thresholds · Exam Mnemonics")
c.setFont("Helvetica-Oblique", 8)
c.setFillColor(GOLD)
c.drawCentredString(self.W/2, self.H - 3.15*cm, "Oral Medicine & Oral Radiology | BDS / MDS Viva Preparation")
class XRayTubeDiagram(Flowable):
"""Schematic of X-ray tube components."""
def __init__(self, w=16*cm, h=7.5*cm):
super().__init__()
self.W = w; self.H = h
def wrap(self, *args): return self.W, self.H
def draw(self):
c = self.canv
W, H = self.W, self.H
# Glass envelope
c.setStrokeColor(TEAL); c.setLineWidth(1.5)
c.setFillColor(colors.HexColor("#e8f8f8"))
c.roundRect(0.3*cm, 0.8*cm, W-0.6*cm, H-1.4*cm, 0.5*cm, fill=1, stroke=1)
# Label
c.setFont("Helvetica-Bold", 8); c.setFillColor(TEAL)
c.drawString(0.5*cm, H-0.55*cm, "Glass/Metal Envelope (Vacuum)")
# Cathode assembly
cx = 1.8*cm
c.setFillColor(NAVY); c.setStrokeColor(NAVY); c.setLineWidth(1)
c.rect(cx-0.35*cm, H/2-1.1*cm, 0.7*cm, 2.2*cm, fill=1, stroke=0)
# Filament coil symbol
c.setStrokeColor(GOLD); c.setLineWidth(2); c.setFillColor(GOLD)
for i in range(5):
y = H/2 - 0.6*cm + i*0.22*cm
c.arc(cx-0.12*cm, y, cx+0.12*cm, y+0.2*cm, 0, 180)
c.setFont("Helvetica-Bold", 8); c.setFillColor(NAVY)
c.drawCentredString(cx, 0.45*cm, "CATHODE")
c.setFont("Helvetica", 7); c.setFillColor(colors.black)
c.drawCentredString(cx, 0.22*cm, "(Tungsten filament)")
# Electron beam arrows
ax_start = cx + 0.4*cm
ax_end = W - 3.2*cm
c.setStrokeColor(GOLD); c.setLineWidth(1.2)
for dy in [-0.25*cm, 0, 0.25*cm]:
c.line(ax_start, H/2+dy, ax_end, H/2+dy)
# arrowhead
c.setFillColor(GOLD)
c.polygon([ax_end, H/2+dy,
ax_end-0.22*cm, H/2+dy+0.1*cm,
ax_end-0.22*cm, H/2+dy-0.1*cm], fill=1)
c.setFont("Helvetica-Oblique", 7); c.setFillColor(GOLD)
c.drawCentredString((ax_start+ax_end)/2, H/2+0.45*cm, "electron beam")
# Anode
atx = W - 2.5*cm
c.setFillColor(colors.HexColor("#b7950b"))
c.polygon([atx, H/2-0.7*cm,
atx+0.9*cm, H/2,
atx, H/2+0.7*cm], fill=1, stroke=0)
c.setFont("Helvetica-Bold", 8); c.setFillColor(NAVY)
c.drawCentredString(atx+0.35*cm, 0.45*cm, "ANODE")
c.setFont("Helvetica", 7); c.setFillColor(colors.black)
c.drawCentredString(atx+0.35*cm, 0.22*cm, "(Tungsten target)")
# X-ray beam downward
bx = atx + 0.05*cm
c.setStrokeColor(RED_ACC); c.setLineWidth(2)
c.line(bx, H/2-0.7*cm, bx, 0.95*cm)
# diverging rays
c.setLineWidth(1)
for angle in [-25, 0, 25]:
rad = math.radians(270 + angle)
ex = bx + 1.4*cm * math.cos(rad)
ey = H/2-0.7*cm + 1.4*cm * math.sin(rad)
c.line(bx, H/2-0.7*cm, ex, ey)
c.setFont("Helvetica-Bold", 7.5); c.setFillColor(RED_ACC)
c.drawCentredString(bx, 0.7*cm, "X-RAY BEAM")
# Focusing cup label
c.setFont("Helvetica", 7); c.setFillColor(PURPLE)
c.drawString(0.1*cm, H/2+1.0*cm, "Focusing cup")
c.setStrokeColor(PURPLE); c.setLineWidth(0.5)
c.line(1.15*cm, H/2+0.9*cm, cx-0.35*cm, H/2+0.7*cm)
# kV label
c.setFont("Helvetica-Bold", 8); c.setFillColor(TEAL)
c.drawCentredString(W/2, H-0.5*cm, "High Voltage (kVp 60-120)")
c.setStrokeColor(TEAL); c.setLineWidth(0.8)
c.line(1.5*cm, H-0.35*cm, W-1.5*cm, H-0.35*cm)
class InverseSquareDiagram(Flowable):
"""Visual of inverse square law with circles."""
def __init__(self, w=13*cm, h=5.5*cm):
super().__init__()
self.W = w; self.H = h
def wrap(self, *args): return self.W, self.H
def draw(self):
c = self.canv
W, H = self.W, self.H
ox, oy = 1.0*cm, H/2
# source
c.setFillColor(GOLD); c.setStrokeColor(GOLD)
c.circle(ox, oy, 0.2*cm, fill=1)
c.setFont("Helvetica-Bold", 7.5); c.setFillColor(NAVY)
c.drawCentredString(ox, oy-0.55*cm, "Source")
distances = [2.5, 5.5, 9.5]
labels = ["d", "2d", "3d"]
intensities = ["I", "I/4", "I/9"]
dot_counts = [9, 4, 2]
dot_color = RED_ACC
for idx, (dx, lbl, ints, ndots) in enumerate(
zip(distances, labels, intensities, dot_counts)):
xpos = ox + dx*cm
# vertical line
c.setStrokeColor(GREY_MID); c.setLineWidth(0.7)
c.line(xpos, oy-1.3*cm, xpos, oy+1.3*cm)
# dots representing intensity
c.setFillColor(dot_color)
cols = int(math.ceil(math.sqrt(ndots)))
for d in range(ndots):
row = d // cols; col = d % cols
px = xpos - 0.25*cm + col*0.18*cm
py = oy + 0.4*cm - row*0.22*cm
c.circle(px, py, 0.055*cm, fill=1, stroke=0)
# labels
c.setFont("Helvetica-Bold", 8.5); c.setFillColor(TEAL)
c.drawCentredString(xpos, oy-1.55*cm, lbl)
c.setFont("Helvetica", 8); c.setFillColor(NAVY)
c.drawCentredString(xpos, oy-1.9*cm, ints)
# rays from source
c.setStrokeColor(GOLD); c.setLineWidth(0.6)
for angle in [-30, -15, 0, 15, 30]:
rad = math.radians(angle)
c.line(ox+0.2*cm, oy,
ox + 10.5*cm*math.cos(rad),
oy + 10.5*cm*math.sin(rad))
# formula
c.setFont("Helvetica-Bold", 10); c.setFillColor(NAVY)
c.drawString(0.1*cm, 0.15*cm, "I \u221d 1 / d\u00b2")
c.setFont("Helvetica", 7.5); c.setFillColor(colors.black)
c.drawString(2.5*cm, 0.15*cm, " Doubling distance \u2192 intensity reduced to 1/4")
class BeamQualityBar(Flowable):
"""Horizontal bar showing kVp effect on beam quality."""
def __init__(self, w=15*cm, h=2.6*cm):
super().__init__()
self.W = w; self.H = h
def wrap(self, *args): return self.W, self.H
def draw(self):
c = self.canv
W, H = self.W, self.H
# gradient bar
steps = 60
for i in range(steps):
t = i/steps
r = colors.HexColor("#3498db").red * (1-t) + RED_ACC.red * t
g = colors.HexColor("#3498db").green * (1-t) + RED_ACC.green * t
b = colors.HexColor("#3498db").blue * (1-t) + RED_ACC.blue * t
c.setFillColorRGB(r, g, b)
c.rect(W*i/steps, H/2-0.3*cm, W/steps+1, 0.6*cm, fill=1, stroke=0)
# border
c.setStrokeColor(NAVY); c.setLineWidth(1)
c.rect(0, H/2-0.3*cm, W, 0.6*cm, fill=0, stroke=1)
# ticks
kvps = [50, 60, 65, 70, 80, 90, 100, 120]
for kv in kvps:
x = W * (kv-50)/(120-50)
c.setStrokeColor(WHITE); c.setLineWidth(0.8)
c.line(x, H/2-0.28*cm, x, H/2+0.28*cm)
c.setFont("Helvetica", 6.5); c.setFillColor(NAVY)
c.drawCentredString(x, H/2-0.55*cm, str(kv))
c.setFont("Helvetica-Bold", 7.5); c.setFillColor(NAVY)
c.drawString(0, 0.1*cm, "50 kVp (Soft / Low energy)")
c.drawRightString(W, 0.1*cm, "120 kVp (Hard / High energy)")
c.setFont("Helvetica-Bold", 8); c.setFillColor(WHITE)
c.drawCentredString(W/2, H/2-0.12*cm, "kVp (kilovoltage peak)")
# filtration markers
c.setStrokeColor(GOLD); c.setLineWidth(1.5)
x70 = W * (70-50)/(120-50)
c.line(x70, H/2+0.3*cm, x70, H/2+0.7*cm)
c.setFont("Helvetica-Bold", 7); c.setFillColor(GOLD)
c.drawCentredString(x70, H/2+0.78*cm, "70 kVp threshold")
c.setFont("Helvetica", 6.5); c.setFillColor(TEAL)
c.drawCentredString(W*0.15, H-0.1*cm, "Filter: 1.5 mm Al")
c.drawCentredString(W*0.7, H-0.1*cm, "Filter: 2.5 mm Al")
class RadiationScaleDiagram(Flowable):
"""Electromagnetic spectrum snippet showing X-ray position."""
def __init__(self, w=15*cm, h=3.2*cm):
super().__init__()
self.W = w; self.H = h
def wrap(self, *args): return self.W, self.H
def draw(self):
c = self.canv
W, H = self.W, self.H
segments = [
("Radio", colors.HexColor("#2980b9"), "10³m"),
("Micro", colors.HexColor("#27ae60"), "1mm"),
("IR", colors.HexColor("#e67e22"), "700nm"),
("Visible",colors.HexColor("#f1c40f"),"400nm"),
("UV", PURPLE, "10nm"),
("X-RAY", RED_ACC, "0.01nm"),
("Gamma", colors.HexColor("#922b21"), "0.001nm"),
]
sw = W / len(segments)
for i, (label, col, wl) in enumerate(segments):
c.setFillColor(col)
c.rect(i*sw, H/2, sw-1, H/2-0.1*cm, fill=1, stroke=0)
c.setFillColor(WHITE if col != colors.HexColor("#f1c40f") else NAVY)
c.setFont("Helvetica-Bold", 7)
c.drawCentredString(i*sw + sw/2, H/2+0.5*cm, label)
c.setFont("Helvetica", 6)
c.setFillColor(NAVY)
c.drawCentredString(i*sw + sw/2, H/2+0.22*cm, wl)
# Arrow: increasing energy
c.setStrokeColor(NAVY); c.setFillColor(NAVY); c.setLineWidth(1)
c.line(0.1*cm, H/2-0.35*cm, W-0.1*cm, H/2-0.35*cm)
c.polygon([W-0.1*cm, H/2-0.35*cm,
W-0.4*cm, H/2-0.22*cm,
W-0.4*cm, H/2-0.48*cm], fill=1)
c.setFont("Helvetica-Bold", 7.5); c.setFillColor(NAVY)
c.drawString(0.1*cm, H/2-0.6*cm, "Increasing Energy / Frequency")
c.drawRightString(W, H/2-0.6*cm, "Decreasing Wavelength")
# X-ray highlight box
xi = 5 * sw
c.setStrokeColor(GOLD); c.setLineWidth(2)
c.rect(xi, H/2, sw-1, H/2-0.1*cm, fill=0, stroke=1)
# ── PAGE HEADER / FOOTER ──────────────────────────────────────────────────────
def on_page(canvas, doc):
canvas.saveState()
# Top bar
canvas.setFillColor(NAVY)
canvas.rect(0, H-0.65*cm, W, 0.65*cm, fill=1, stroke=0)
canvas.setFillColor(GOLD)
canvas.setFont("Helvetica-Bold", 8)
canvas.drawString(1.5*cm, H-0.45*cm, "ORAL RADIOLOGY | Radiation Study Guide")
canvas.setFillColor(LIGHT_BG)
canvas.setFont("Helvetica", 8)
canvas.drawRightString(W-1.5*cm, H-0.45*cm, f"Page {doc.page}")
# Bottom bar
canvas.setFillColor(TEAL)
canvas.rect(0, 0, W, 0.4*cm, fill=1, stroke=0)
canvas.setFillColor(WHITE)
canvas.setFont("Helvetica", 6.5)
canvas.drawCentredString(W/2, 0.12*cm, "BDS/MDS Viva Preparation | Oral Medicine & Oral Radiology")
canvas.restoreState()
def on_first_page(canvas, doc):
canvas.saveState()
canvas.setFillColor(TEAL)
canvas.rect(0, 0, W, 0.4*cm, fill=1, stroke=0)
canvas.setFillColor(WHITE); canvas.setFont("Helvetica", 6.5)
canvas.drawCentredString(W/2, 0.12*cm, "BDS/MDS Viva Preparation | Oral Medicine & Oral Radiology")
canvas.restoreState()
# ── BUILD ─────────────────────────────────────────────────────────────────────
def build():
path = "/home/daytona/workspace/radiation-study/Radiation_Study_Guide.pdf"
doc = SimpleDocTemplate(
path, pagesize=A4,
leftMargin=2*cm, rightMargin=2*cm,
topMargin=1.2*cm, bottomMargin=1.2*cm,
title="Oral Radiology Radiation Study Guide",
author="Oral Radiology Viva Prep"
)
story = []
CW = W - 4*cm # content width
# ═══════════════════════════════════════════════════════════════════════
# TITLE BLOCK
# ═══════════════════════════════════════════════════════════════════════
story.append(TitleBlock(CW, 3.8*cm))
story.append(sp(1.5))
# Quick nav pills (simulated as coloured table cells)
nav_data = [["X-RAY TUBE", "RADIATION UNITS", "FILTRATION", "DOSE LIMITS", "INVERSE SQ.", "EM SPECTRUM"]]
nav_cols = [CW/6]*6
nav_t = Table(nav_data, colWidths=nav_cols)
nav_t.setStyle(TableStyle([
("BACKGROUND", (0,0),(0,0), TEAL),
("BACKGROUND", (1,0),(1,0), NAVY),
("BACKGROUND", (2,0),(2,0), TEAL),
("BACKGROUND", (3,0),(3,0), RED_ACC),
("BACKGROUND", (4,0),(4,0), NAVY),
("BACKGROUND", (5,0),(5,0), TEAL),
("TEXTCOLOR", (0,0),(-1,-1), WHITE),
("FONTNAME", (0,0),(-1,-1), "Helvetica-Bold"),
("FONTSIZE", (0,0),(-1,-1), 7),
("ALIGN", (0,0),(-1,-1), "CENTER"),
("TOPPADDING", (0,0),(-1,-1), 4),
("BOTTOMPADDING",(0,0),(-1,-1), 4),
]))
story.append(nav_t)
story.append(sp(1.5))
# ═══════════════════════════════════════════════════════════════════════
# SECTION 1 – X-RAY TUBE DIAGRAM
# ═══════════════════════════════════════════════════════════════════════
story.append(hdr_para(" 1. X-RAY TUBE — COMPONENTS & LABELLED DIAGRAM", bg=NAVY))
story.append(sp())
story.append(XRayTubeDiagram(CW, 7.5*cm))
story.append(sp())
# Components table
comp_data = [
[Paragraph("Component", CELL_HDR), Paragraph("Material", CELL_HDR),
Paragraph("Function", CELL_HDR), Paragraph("Key Fact", CELL_HDR)],
[Paragraph("Filament", CELL_BL), Paragraph("Tungsten wire", CELL_S),
Paragraph("Thermionic emission — produces electrons when heated to ~2200°C", CELL_L),
Paragraph("Heated by step-down transformer (3-5 V)", CELL_L)],
[Paragraph("Focusing cup", CELL_BL), Paragraph("Molybdenum", CELL_S),
Paragraph("Negative charge concentrates electron beam onto focal spot", CELL_L),
Paragraph("Reduces focal spot size → sharper image", CELL_L)],
[Paragraph("Anode (target)", CELL_BL), Paragraph("Tungsten (Z=74)", CELL_S),
Paragraph("Electrons strike target → X-rays produced (Bremsstrahlung + characteristic)", CELL_L),
Paragraph("High Z + high melting point (3422°C)", CELL_L)],
[Paragraph("Glass/metal envelope", CELL_BL), Paragraph("Borosilicate / metal", CELL_S),
Paragraph("Maintains vacuum; prevents electron scattering", CELL_L),
Paragraph("~10⁻⁶ mmHg vacuum", CELL_L)],
[Paragraph("Step-up transformer", CELL_BL), Paragraph("Copper coils", CELL_S),
Paragraph("Increases voltage to 60-120 kVp for X-ray production", CELL_L),
Paragraph("Controls quality (kVp)", CELL_L)],
[Paragraph("Step-down transformer", CELL_BL), Paragraph("Copper coils", CELL_S),
Paragraph("Reduces voltage to 3-5 V to heat the filament", CELL_L),
Paragraph("Controls quantity (mA)", CELL_L)],
[Paragraph("Insulating oil", CELL_BL), Paragraph("Transformer oil", CELL_S),
Paragraph("Heat dissipation + electrical insulation", CELL_L),
Paragraph("Surrounds X-ray tube inside tubehead", CELL_L)],
[Paragraph("Al filter", CELL_BL), Paragraph("Aluminum (Al)", CELL_S),
Paragraph("Removes low-energy (long λ) X-rays that irradiate patient but add no image info", CELL_L),
Paragraph("1.5 mm (<70kVp) / 2.5 mm (≥70kVp)", CELL_L)],
[Paragraph("Collimator", CELL_BL), Paragraph("Lead", CELL_S),
Paragraph("Restricts beam size to area of interest; reduces patient dose and scatter", CELL_L),
Paragraph("Round ≤7cm; Rect saves 60-70% dose", CELL_L)],
]
comp_t = box_table(comp_data,
col_widths=[2.5*cm, 2.3*cm, 6.0*cm, 4.0*cm],
hdr_bg=TEAL)
story.append(comp_t)
story.append(sp())
# ═══════════════════════════════════════════════════════════════════════
# SECTION 2 – X-RAY PRODUCTION
# ═══════════════════════════════════════════════════════════════════════
story.append(sp(0.5))
story.append(hdr_para(" 2. X-RAY PRODUCTION — BREMSSTRAHLUNG vs CHARACTERISTIC", bg=TEAL))
story.append(sp())
prod_data = [
[Paragraph("Feature", CELL_HDR), Paragraph("Bremsstrahlung (Braking Radiation)", CELL_HDR),
Paragraph("Characteristic Radiation", CELL_HDR)],
[Paragraph("Mechanism", CELL_BL),
Paragraph("Electrons decelerate near nucleus; kinetic energy → X-ray photon", CELL_L),
Paragraph("Electrons eject inner-shell electrons; outer shell fills vacancy → photon", CELL_L)],
[Paragraph("Spectrum", CELL_BL), Paragraph("Continuous spectrum", CELL_S),
Paragraph("Line spectrum (discrete energies)", CELL_S)],
[Paragraph("% of X-rays", CELL_BL), Paragraph("~99% of dental X-rays", CELL_S),
Paragraph("~1% (only when kVp exceeds binding energy)", CELL_S)],
[Paragraph("Energy range", CELL_BL), Paragraph("0 → max (kVp)", CELL_S),
Paragraph("Fixed energies specific to target element", CELL_S)],
[Paragraph("Dependency", CELL_BL), Paragraph("Depends on kVp and Z of target", CELL_S),
Paragraph("Depends on target element (Z)", CELL_S)],
[Paragraph("Tungsten K-edge", CELL_BL), Paragraph("N/A", CELL_S),
Paragraph("Kα = 59.3 keV; Kβ = 67.2 keV (for W)", CELL_S)],
]
story.append(box_table(prod_data, [2.5*cm, 5.5*cm, 5.5*cm], hdr_bg=NAVY))
story.append(sp(1.5))
# ═══════════════════════════════════════════════════════════════════════
# SECTION 3 – RADIATION UNITS CONVERSION TABLE
# ═══════════════════════════════════════════════════════════════════════
story.append(PageBreak())
story.append(hdr_para(" 3. RADIATION UNITS — COMPLETE CONVERSION TABLE", bg=NAVY))
story.append(sp())
units_data = [
[Paragraph("Quantity", CELL_HDR), Paragraph("Old Unit", CELL_HDR),
Paragraph("SI Unit", CELL_HDR), Paragraph("Conversion", CELL_HDR),
Paragraph("Definition / Notes", CELL_HDR)],
# Exposure
[Paragraph("EXPOSURE", ParagraphStyle("cat", fontName="Helvetica-Bold", fontSize=8,
textColor=WHITE, alignment=TA_LEFT, leading=10)),
Paragraph("Roentgen (R)", CELL_S), Paragraph("Coulomb/kg (C/kg)", CELL_S),
Paragraph("1 R = 2.58 × 10⁻⁴ C/kg", CELL_L),
Paragraph("Ionization of air; only for X-rays and gamma rays in air", CELL_L)],
# Absorbed dose
[Paragraph("ABSORBED DOSE", ParagraphStyle("cat", fontName="Helvetica-Bold", fontSize=8,
textColor=WHITE, alignment=TA_LEFT, leading=10)),
Paragraph("rad", CELL_S), Paragraph("Gray (Gy)", CELL_S),
Paragraph("1 Gy = 100 rad\n1 rad = 0.01 Gy\n1 mGy = 100 mrad", CELL_L),
Paragraph("Energy absorbed per unit mass (J/kg); applies to any radiation in any material", CELL_L)],
# Equivalent dose
[Paragraph("EQUIVALENT DOSE", ParagraphStyle("cat", fontName="Helvetica-Bold", fontSize=8,
textColor=WHITE, alignment=TA_LEFT, leading=10)),
Paragraph("rem", CELL_S), Paragraph("Sievert (Sv)", CELL_S),
Paragraph("1 Sv = 100 rem\n1 rem = 0.01 Sv\n1 mSv = 100 mrem", CELL_L),
Paragraph("Absorbed dose × radiation weighting factor (Wr); accounts for biological effectiveness", CELL_L)],
# Effective dose
[Paragraph("EFFECTIVE DOSE", ParagraphStyle("cat", fontName="Helvetica-Bold", fontSize=8,
textColor=WHITE, alignment=TA_LEFT, leading=10)),
Paragraph("rem", CELL_S), Paragraph("Sievert (Sv)", CELL_S),
Paragraph("Same unit as equivalent dose", CELL_L),
Paragraph("Equivalent dose × tissue weighting factor (Wt); whole-body risk estimate", CELL_L)],
# Activity
[Paragraph("ACTIVITY", ParagraphStyle("cat", fontName="Helvetica-Bold", fontSize=8,
textColor=WHITE, alignment=TA_LEFT, leading=10)),
Paragraph("Curie (Ci)", CELL_S), Paragraph("Becquerel (Bq)", CELL_S),
Paragraph("1 Ci = 3.7×10¹⁰ Bq\n1 Bq = 2.7×10⁻¹¹ Ci", CELL_L),
Paragraph("Rate of radioactive decay; 1 Bq = 1 disintegration/sec", CELL_L)],
]
# colour alternating rows by category
units_t = Table(units_data, colWidths=[2.6*cm, 2.0*cm, 2.2*cm, 3.5*cm, 4.5*cm])
cat_bg = NAVY
units_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), TEAL),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("BACKGROUND", (0,1), (-1,1), NAVY),
("BACKGROUND", (0,2), (-1,2), TEAL),
("BACKGROUND", (0,3), (-1,3), NAVY),
("BACKGROUND", (0,4), (-1,4), TEAL),
("BACKGROUND", (0,5), (-1,5), NAVY),
("TEXTCOLOR", (0,1), (0,-1), WHITE),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#aaaaaa")),
("FONTSIZE", (0,0), (-1,-1), 8),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("ROWBACKGROUNDS",(0,0), (-1,-1), [TEAL, NAVY]),
("TEXTCOLOR", (1,1), (-1,-1), WHITE),
]))
story.append(units_t)
story.append(sp())
# Radiation Weighting Factors
story.append(hdr_para(" 3a. RADIATION WEIGHTING FACTORS (Wr) — ICRP 2007", bg=TEAL))
story.append(sp())
wr_data = [
[Paragraph("Radiation Type", CELL_HDR), Paragraph("Weighting Factor (Wr)", CELL_HDR),
Paragraph("Notes", CELL_HDR)],
["X-rays, Gamma rays, Beta", Paragraph("1", CELL_S), "Least damaging per unit absorbed dose"],
["Protons (>2 MeV)", Paragraph("2", CELL_S), "Moderate biological effectiveness"],
["Neutrons < 1 MeV", Paragraph("2.5 – 20", CELL_S), "Depends on energy; most variable"],
["Neutrons 1–50 MeV", Paragraph("10 – 20", CELL_S), "High energy neutrons most damaging"],
["Alpha particles, fission fragments", Paragraph("20", CELL_S), "Most biologically damaging (short range, dense ionization)"],
]
story.append(box_table(wr_data, [4.5*cm, 3.5*cm, 6.8*cm], hdr_bg=NAVY))
story.append(sp())
# Tissue Weighting Factors
story.append(hdr_para(" 3b. TISSUE WEIGHTING FACTORS (Wt) — ICRP 2007", bg=TEAL))
story.append(sp())
wt_data = [
[Paragraph("Wt", CELL_HDR), Paragraph("Tissues/Organs", CELL_HDR)],
["0.12 (each)", "Bone marrow (red), Colon, Lung, Stomach, Breast, Remainder tissues"],
["0.08 (each)", "Gonads"],
["0.04 (each)", "Urinary bladder, Oesophagus, Liver, Thyroid"],
["0.01 (each)", "Bone surface, Brain, Salivary glands, Skin"],
["SUM = 1.00", "Total body weighting factor"],
]
story.append(box_table(wt_data, [3*cm, 11.8*cm], hdr_bg=NAVY))
story.append(sp(0.5))
story.append(Paragraph("<font color='#c0392b'><b>Dental relevance:</b></font> Thyroid Wt = 0.04; Bone marrow Wt = 0.12; Salivary glands Wt = 0.01. "
"Use thyroid shield + lead apron to protect these tissues.", BODY_S))
story.append(sp(1.5))
# ═══════════════════════════════════════════════════════════════════════
# SECTION 4 – DOSE LIMITS
# ═══════════════════════════════════════════════════════════════════════
story.append(PageBreak())
story.append(hdr_para(" 4. RADIATION DOSE LIMITS (ICRP 2007 / AERB India)", bg=NAVY))
story.append(sp())
dose_data = [
[Paragraph("Category", CELL_HDR), Paragraph("Effective Dose Limit", CELL_HDR),
Paragraph("Equivalent Dose – Lens of Eye", CELL_HDR),
Paragraph("Equivalent Dose – Skin / Extremities", CELL_HDR)],
[Paragraph("Radiation workers\n(occupational)", CELL_BL),
Paragraph("20 mSv/year (averaged over 5 years)\nMax 50 mSv in any single year", CELL_S),
Paragraph("20 mSv/year\n(ICRP 2011 update)", CELL_S),
Paragraph("500 mSv/year", CELL_S)],
[Paragraph("Pregnant radiation worker", CELL_BL),
Paragraph("1 mSv for remainder of pregnancy\n(abdomen surface)", CELL_S),
Paragraph("—", CELL_S), Paragraph("—", CELL_S)],
[Paragraph("General public", CELL_BL),
Paragraph("1 mSv/year", CELL_S),
Paragraph("15 mSv/year\n(old ICRP 60 = 150 mSv)", CELL_S),
Paragraph("50 mSv/year", CELL_S)],
[Paragraph("Students (<18 yrs)", CELL_BL),
Paragraph("6 mSv/year (if using radiation)", CELL_S),
Paragraph("15 mSv/year", CELL_S),
Paragraph("50 mSv/year", CELL_S)],
]
story.append(box_table(dose_data, [3.2*cm, 4.5*cm, 3.5*cm, 3.6*cm], hdr_bg=RED_ACC))
story.append(sp())
# Typical dental doses table
story.append(hdr_para(" 4a. TYPICAL EFFECTIVE DOSES — DENTAL RADIOGRAPHY", bg=TEAL))
story.append(sp())
dental_dose_data = [
[Paragraph("Radiograph", CELL_HDR), Paragraph("Approximate Effective Dose", CELL_HDR),
Paragraph("Equivalent Background Radiation", CELL_HDR)],
["Periapical (D-speed film)", "~8 µSv", "~1 day natural background"],
["Periapical (F-speed / digital)", "~1-2 µSv", "<1 day"],
["Bitewing (digital)", "~5 µSv", "~17 hours"],
["Full mouth series (18 films, rect. collimation)", "~35 µSv", "~4 days"],
["Panoramic (OPG)", "~14-24 µSv", "~2-3 days"],
["Lateral cephalogram", "~5-6 µSv", "~20 hours"],
["CBCT (small FOV)", "~40-100 µSv", "~1-2 weeks"],
["CBCT (large FOV)", "~100-600 µSv", "~2-8 weeks"],
["Chest X-ray (for comparison)", "~20 µSv", "~3 days"],
["Annual natural background (India avg.)", "~2400 µSv (2.4 mSv)", "365 days"],
]
story.append(box_table(dental_dose_data, [4.5*cm, 4.5*cm, 5.8*cm], hdr_bg=NAVY))
story.append(sp(1.5))
# ═══════════════════════════════════════════════════════════════════════
# SECTION 5 – FILTRATION TABLE
# ═══════════════════════════════════════════════════════════════════════
story.append(hdr_para(" 5. FILTRATION — COMPLETE REFERENCE TABLE", bg=NAVY))
story.append(sp())
story.append(BeamQualityBar(CW, 2.6*cm))
story.append(sp())
filt_data = [
[Paragraph("Type", CELL_HDR), Paragraph("Source", CELL_HDR),
Paragraph("Composition", CELL_HDR), Paragraph("Amount", CELL_HDR), Paragraph("Notes", CELL_HDR)],
["Inherent filtration", "Built into tube", "Glass envelope + oil + tubehead seal",
"~0.5–1.0 mm Al equiv.", "Cannot be altered; fixed"],
["Added filtration", "External disk at port", "Aluminum (most common)\nCopper (high kVp units)",
"Variable", "Can be changed; added by manufacturer/operator"],
["Total filtration", "Inherent + Added", "Combined",
"≥1.5 mm Al (<70 kVp)\n≥2.5 mm Al (≥70 kVp)",
"NCRP / BDA / AERB requirement"],
["Thoraeus filter", "Therapeutic units", "Tin (Sn) + Cu + Al",
"Used in radiotherapy", "K-edge filter; hardens beam maximally"],
["Compensating filter", "Panoramic / Ceph.", "Wedge/step shape Al",
"Variable", "Equalises beam for different tissue thicknesses"],
]
story.append(box_table(filt_data, [2.8*cm, 2.5*cm, 3.2*cm, 3.0*cm, 3.3*cm], hdr_bg=TEAL))
story.append(sp())
# Collimation comparison
story.append(hdr_para(" 5a. COLLIMATION TYPES — COMPARISON", bg=TEAL))
story.append(sp())
col_data = [
[Paragraph("Type", CELL_HDR), Paragraph("Shape", CELL_HDR),
Paragraph("Beam Size at Skin", CELL_HDR), Paragraph("Dose Reduction", CELL_HDR),
Paragraph("Scatter Reduction", CELL_HDR), Paragraph("Use", CELL_HDR)],
["Round / Circular", "Circle", "≤7 cm diameter", "Baseline", "Moderate", "Intraoral (routine)"],
["Rectangular", "Rectangle (~3×4 cm)", "Matches film size", "60–70% vs round", "Maximum", "BEST practice"],
["Cylindrical cone", "Open cylinder", "Round (variable)", "Moderate", "Good", "Beam indicator / PID"],
["Lead diaphragm", "Adjustable aperture", "Variable", "Adjustable", "Adjustable", "Medical units"],
]
story.append(box_table(col_data, [2.5*cm, 2.5*cm, 2.8*cm, 2.5*cm, 2.3*cm, 2.2*cm], hdr_bg=NAVY))
story.append(sp(1.5))
# ═══════════════════════════════════════════════════════════════════════
# SECTION 6 – INVERSE SQUARE LAW
# ═══════════════════════════════════════════════════════════════════════
story.append(PageBreak())
story.append(hdr_para(" 6. INVERSE SQUARE LAW — FORMULA, DIAGRAM & WORKED EXAMPLES", bg=NAVY))
story.append(sp())
story.append(InverseSquareDiagram(CW, 5.5*cm))
story.append(sp())
# Formula box
formula_data = [[
Paragraph("<b>I₁ / I₂ = D₂² / D₁²</b>",
ParagraphStyle("form", fontName="Helvetica-Bold", fontSize=13,
textColor=NAVY, alignment=TA_CENTER)),
Paragraph("Where:<br/>I = Intensity (radiation exposure rate)<br/>"
"D = Distance from source<br/>"
"Subscript 1 = original; 2 = new position",
ParagraphStyle("formn", fontName="Helvetica", fontSize=9,
textColor=colors.black, leading=14)),
]]
ft = Table(formula_data, colWidths=[CW*0.4, CW*0.6])
ft.setStyle(TableStyle([
("BACKGROUND", (0,0),(0,0), LIGHT_BG),
("BACKGROUND", (1,0),(1,0), GREY_LT),
("GRID", (0,0),(-1,-1), 1, TEAL),
("VALIGN", (0,0),(-1,-1), "MIDDLE"),
("LEFTPADDING", (0,0),(-1,-1), 10),
("TOPPADDING", (0,0),(-1,-1), 10),
("BOTTOMPADDING", (0,0),(-1,-1), 10),
]))
story.append(ft)
story.append(sp())
# Worked examples
ex_data = [
[Paragraph("Scenario", CELL_HDR), Paragraph("Given", CELL_HDR),
Paragraph("Solution", CELL_HDR), Paragraph("Answer", CELL_HDR)],
["Distance doubled (d → 2d)", "I₁, D₁=d, D₂=2d",
"I₂ = I₁ × (d/2d)² = I₁ × ¼", "Intensity = I/4 (75% reduction)"],
["Distance tripled (d → 3d)", "I₁, D₁=d, D₂=3d",
"I₂ = I₁ × (d/3d)² = I₁ × 1/9", "Intensity = I/9 (89% reduction)"],
["Distance halved (d → d/2)", "I₁, D₁=d, D₂=d/2",
"I₂ = I₁ × (d/0.5d)² = I₁ × 4", "Intensity = 4I (quadrupled)"],
["Operator protection (6 ft rule)", "D=6 ft from source",
"I₂ = I₁ × (1ft/6ft)² = I₁/36", "Intensity at 6 ft = 1/36th of source"],
["PID change: 8 in → 16 in", "mA, kVp constant; D doubles",
"I₂ = I₁/4; compensate by 4× mAs", "Exposure time/mAs must be quadrupled"],
]
story.append(box_table(ex_data, [3.5*cm, 3.0*cm, 5.0*cm, 3.3*cm], hdr_bg=TEAL))
story.append(sp(1.5))
# ═══════════════════════════════════════════════════════════════════════
# SECTION 7 – EM SPECTRUM
# ═══════════════════════════════════════════════════════════════════════
story.append(hdr_para(" 7. ELECTROMAGNETIC SPECTRUM — X-RAY POSITION", bg=NAVY))
story.append(sp())
story.append(RadiationScaleDiagram(CW, 3.2*cm))
story.append(sp())
em_data = [
[Paragraph("Property", CELL_HDR), Paragraph("X-Ray Values (Diagnostic)", CELL_HDR),
Paragraph("Notes", CELL_HDR)],
["Wavelength", "0.01 – 0.5 nm (10 – 500 pm)", "Shorter λ = harder beam = higher energy"],
["Frequency", "6×10¹⁷ – 3×10¹⁹ Hz", "f = c/λ"],
["Energy (keV)", "10 – 150 keV", "Dental: ~60-90 keV peak energy"],
["Speed", "3 × 10⁸ m/s (speed of light)", "Same for all EM radiation"],
["Nature", "Electromagnetic (transverse waves, photons)", "No mass, no charge"],
["Ionisation", "Yes — directly ionising", "Knocks orbital electrons → ion pairs"],
["Min. wavelength λmin", "λmin = 12.4 / kVp (in Angstroms)", "Duane-Hunt law; higher kVp → shorter λmin"],
]
story.append(box_table(em_data, [3.5*cm, 5.5*cm, 5.8*cm], hdr_bg=TEAL))
story.append(sp(1.5))
# ═══════════════════════════════════════════════════════════════════════
# SECTION 8 – BIOLOGICAL EFFECTS
# ═══════════════════════════════════════════════════════════════════════
story.append(hdr_para(" 8. BIOLOGICAL EFFECTS — CLASSIFICATION & THRESHOLDS", bg=NAVY))
story.append(sp())
bio_data = [
[Paragraph("Feature", CELL_HDR), Paragraph("Stochastic Effects", CELL_HDR),
Paragraph("Deterministic (Tissue) Effects", CELL_HDR)],
["Definition", "Probabilistic — chance of occurrence increases with dose",
"Severity increases with dose once threshold exceeded"],
["Threshold dose", "NONE — any dose carries some risk",
"YES — threshold must be exceeded"],
["Severity vs. dose", "Severity FIXED (does not change with dose)",
"Severity INCREASES with dose above threshold"],
["Examples", "Cancer induction, genetic mutations, heritable effects",
"Cataract (>0.5 Gy lens), epilation, skin erythema, radiation sickness, sterility"],
["Threshold doses", "None",
"Temporary epilation: 3-5 Gy\nPermanent epilation: >7 Gy\nCataract: >0.5 Gy (ICRP 2011)\nAcute radiation sickness: >1 Gy whole body\nLD50/30: ~3-5 Gy whole body"],
["Protection principle", "ALARA — As Low As Reasonably Achievable",
"Keep below threshold; dose limits set at fraction of threshold"],
["Relevant law", "Bergonie & Tribondeau law: more radiosensitive if rapidly dividing, undifferentiated, long mitotic future",
"Same law applies; threshold-based protection is key"],
]
story.append(box_table(bio_data, [3.0*cm, 5.5*cm, 6.3*cm], hdr_bg=RED_ACC))
story.append(sp())
# Cell sensitivity order
story.append(hdr_para(" 8a. RADIOSENSITIVITY ORDER (HIGH → LOW)", bg=TEAL))
story.append(sp())
sens_data = [
[Paragraph("Radiosensitivity", CELL_HDR), Paragraph("Cell / Tissue Types", CELL_HDR)],
[Paragraph("HIGH", ParagraphStyle("hs", fontName="Helvetica-Bold", fontSize=8,
textColor=WHITE, alignment=TA_CENTER, leading=10)),
"Lymphocytes, Bone marrow (stem cells), Gonads (spermatogonia, oocytes), "
"Intestinal crypts, Lens epithelium"],
[Paragraph("MODERATE", ParagraphStyle("ms", fontName="Helvetica-Bold", fontSize=8,
textColor=WHITE, alignment=TA_CENTER, leading=10)),
"Salivary gland acinar cells (esp. serous/parotid), Endothelial cells, "
"Fibroblasts, Oral mucosa, Skin basal cells"],
[Paragraph("LOW", ParagraphStyle("ls", fontName="Helvetica-Bold", fontSize=8,
textColor=WHITE, alignment=TA_CENTER, leading=10)),
"Muscle cells, Mature RBCs, Nerve cells (neurons), Cartilage, "
"Mature bone (osteocytes)"],
]
sens_t = Table(sens_data, colWidths=[2.5*cm, 12.3*cm])
sens_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), NAVY),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("BACKGROUND", (0,1), (0,1), RED_ACC),
("BACKGROUND", (0,2), (0,2), ORANGE),
("BACKGROUND", (0,3), (0,3), GREEN_ACC),
("TEXTCOLOR", (0,1), (0,-1), WHITE),
("FONTNAME", (1,1), (1,-1), "Helvetica"),
("FONTSIZE", (0,0), (-1,-1), 8),
("GRID", (0,0), (-1,-1), 0.5, GREY_MID),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 6),
]))
story.append(sens_t)
story.append(sp(1.5))
# ═══════════════════════════════════════════════════════════════════════
# SECTION 9 – FACTORS CONTROLLING BEAM (SUMMARY)
# ═══════════════════════════════════════════════════════════════════════
story.append(PageBreak())
story.append(hdr_para(" 9. FACTORS CONTROLLING X-RAY BEAM — QUALITY & QUANTITY", bg=NAVY))
story.append(sp())
ctrl_data = [
[Paragraph("Factor", CELL_HDR), Paragraph("Affects Quality\n(Penetrating Power)", CELL_HDR),
Paragraph("Affects Quantity\n(Number of Photons)", CELL_HDR),
Paragraph("Effect on Patient Dose", CELL_HDR), Paragraph("Effect on Image", CELL_HDR)],
[Paragraph("↑ kVp", CELL_BL), "YES — harder beam, shorter λ",
"YES — also increases quantity", "↑ dose (but less skin dose if filter used)",
"↓ contrast; ↑ grey shades"],
[Paragraph("↑ mA", CELL_BL), "NO", "YES — more electrons → more X-rays",
"↑ dose proportionally", "↑ density/darkness"],
[Paragraph("↑ Time", CELL_BL), "NO", "YES — longer exposure = more X-rays",
"↑ dose proportionally", "↑ density; ↑ motion blur risk"],
[Paragraph("↑ mAs (mA×t)", CELL_BL), "NO", "YES", "↑ dose", "↑ density"],
[Paragraph("↑ Filtration (Al)", CELL_BL), "YES — removes soft X-rays",
"Slight ↓ (soft X-rays removed)", "↓ skin dose significantly",
"Slight ↓ contrast; better quality"],
[Paragraph("↑ Distance (SID)", CELL_BL), "NO", "YES — reduced by inverse sq. law",
"↓ dose (1/d²)", "↓ magnification; ↑ sharpness"],
[Paragraph("Rectangular collimation", CELL_BL), "NO", "NO",
"↓ 60-70% vs round", "↓ scatter → ↑ contrast"],
[Paragraph("↑ Target Z number", CELL_BL), "YES — more efficient X-ray production",
"YES", "N/A (fixed by design)", "N/A"],
]
story.append(box_table(ctrl_data, [2.5*cm, 2.8*cm, 2.8*cm, 2.8*cm, 3.9*cm], hdr_bg=TEAL))
story.append(sp(1.5))
# ═══════════════════════════════════════════════════════════════════════
# SECTION 10 – RADIATION PROTECTION RULES
# ═══════════════════════════════════════════════════════════════════════
story.append(hdr_para(" 10. RADIATION PROTECTION — RULES, DISTANCES & GUIDELINES", bg=NAVY))
story.append(sp())
prot_data = [
[Paragraph("Rule / Guideline", CELL_HDR), Paragraph("Requirement", CELL_HDR),
Paragraph("Rationale", CELL_HDR)],
["Operator distance", "Minimum 6 feet (1.8 m) from X-ray tube",
"Scatter radiation drops to negligible levels at 6 ft (inverse sq. law → 1/36 of source)"],
["Operator angle", "Stand at 90°–135° to primary beam",
"Primary beam at 0°; scatter is least at 90°–135°; never stand in primary beam path"],
["Protective barriers", "2.5 mm Pb equivalent (primary barrier)\n1.5 mm Pb (secondary barrier)",
"For walls/partitions between operator and X-ray source"],
["Lead apron for patient", "0.25 mm Pb equivalent (thyroid collar + apron)",
"Protects thyroid (Wt=0.04) and bone marrow (Wt=0.12)"],
["Film holding devices", "ALWAYS use film holder / XCP / hemostat",
"Never hold film with fingers in primary beam"],
["ALARA principle", "As Low As Reasonably Achievable",
"Minimise dose with best technique, fastest film, digital sensors, rectangular collimation"],
["Pregnant patients", "Delay if possible; if essential — lead apron + thyroid collar mandatory",
"Embryo/foetus highly radiosensitive (stochastic + deterministic risk)"],
["Film speed", "Use fastest available (F-speed / digital sensor)",
"F-speed ~60% less dose than D-speed; digital 50-80% less than film"],
["CBCT restriction", "Only when conventional radiography inadequate",
"CBCT doses 5–100x higher than OPG; justify each exposure (JUSTIFICATION principle)"],
]
story.append(box_table(prot_data, [3.5*cm, 5.0*cm, 6.3*cm], hdr_bg=RED_ACC))
story.append(sp(1.5))
# ═══════════════════════════════════════════════════════════════════════
# SECTION 11 – MNEMONICS & QUICK RECALL
# ═══════════════════════════════════════════════════════════════════════
story.append(hdr_para(" 11. MNEMONICS & EXAM QUICK-RECALL", bg=NAVY))
story.append(sp())
mnem_data = [
[Paragraph("Topic", CELL_HDR), Paragraph("Mnemonic / Memory Aid", CELL_HDR),
Paragraph("What it means", CELL_HDR)],
["X-ray tube components", "F-FAT (Filament, Focusing cup, Anode, Tube envelope)",
"4 core components of the X-ray tube"],
["Clark's / SLOB rule", "SLOB = Same Lingual Opposite Buccal",
"Object on LINGUAL side moves SAME direction as tube shift; BUCCAL moves OPPOSITE"],
["X-ray properties", "PIPE-SCIF\n(Penetrate, Ionise, Photographic, Excite-fluoresce,\nStraight lines, Cause bio damage, Invisible, Fast=c)",
"All key properties of X-rays in one word"],
["Bergonie & Tribondeau", "MORE = More radiosensitive if:\nMitotic rate high, Oocyte-like (undifferentiated),\nReproduction likely (long mitotic future), Evolving rapidly",
"Law of radiosensitivity"],
["Radiation units (old→SI)", "R → C/kg | rad → Gy | rem → Sv | Ci → Bq",
"Divide rem/rad by 100 to get Sv/Gy; 1 Ci = 3.7×10¹⁰ Bq"],
["Filtration requirements", "<70 kVp = 1.5 mm Al | ≥70 kVp = 2.5 mm Al",
"More kVp needs more filtration"],
["Stochastic vs deterministic", "S = no-threshold, Same severity; D = has threshold, Dose-dependent severity",
"S for Stochastic = no Safe dose; D for Deterministic = Dose threshold"],
["Dose limit (worker)", "20 mSv/year average; max 50 mSv single year",
"ICRP 2007 occupational limit"],
["Collimation dose saving", "Rectangular saves 60-70%; Round max 7 cm",
"Always prefer rectangular for minimal dose"],
["Inverse square law quick calc", "2× distance → ¼ intensity; 3× → 1/9; ½ → 4×",
"Square the ratio of distances (reciprocal)"],
]
story.append(box_table(mnem_data, [3.2*cm, 5.5*cm, 6.1*cm], hdr_bg=PURPLE))
story.append(sp(1.5))
# ═══════════════════════════════════════════════════════════════════════
# SECTION 12 – MASTER KEY-NUMBERS REFERENCE CARD
# ═══════════════════════════════════════════════════════════════════════
story.append(hdr_para(" 12. MASTER KEY-NUMBERS REFERENCE CARD", bg=TEAL))
story.append(sp())
kn_data = [
[Paragraph("Parameter", CELL_HDR), Paragraph("Value", CELL_HDR),
Paragraph("Parameter", CELL_HDR), Paragraph("Value", CELL_HDR)],
["Speed of X-rays", "3×10⁸ m/s",
"Tungsten melting point", "3422°C"],
["Tungsten atomic number", "74",
"Tungsten filament temp (thermionic emission)", "~2200°C"],
["Anode angle (dental)", "~20° (line focus principle)",
"Dental kVp range", "60–90 kVp"],
["Total filtration <70 kVp", "≥1.5 mm Al equiv.",
"Total filtration ≥70 kVp", "≥2.5 mm Al equiv."],
["Round collimator max dia.", "≤7 cm at skin",
"Rect. collimation dose saving", "60–70%"],
["Operator distance", "≥6 feet (1.8 m)",
"Operator angle to beam", "90–135°"],
["Occupational dose limit", "20 mSv/year (avg)",
"Max single year (occupational)", "50 mSv"],
["Public dose limit", "1 mSv/year",
"Pregnant worker abdominal limit", "1 mSv (remainder)"],
["Cataract threshold (ICRP 2011)", "0.5 Gy",
"LD50/30 whole body", "~3–5 Gy"],
["1 Gy =", "100 rad",
"1 Sv =", "100 rem"],
["1 Ci =", "3.7×10¹⁰ Bq",
"1 R =", "2.58×10⁻⁴ C/kg"],
["Digital sensor dose saving vs F-film", "50–80% reduction",
"F-speed vs D-speed saving", "~60% reduction"],
["Periapical dose (digital)", "~1–2 µSv",
"OPG (panoramic) dose", "~14–24 µSv"],
["CBCT small FOV dose", "~40–100 µSv",
"Annual background (India)", "~2400 µSv"],
["Duane-Hunt law", "λmin = 12.4 / kVp (Å)",
"Wr for X-rays", "1"],
["Wt for thyroid", "0.04",
"Wt for bone marrow", "0.12"],
]
kn_t = Table(kn_data, colWidths=[CW*0.28, CW*0.22, CW*0.28, CW*0.22])
kn_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), NAVY),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 8),
("GRID", (0,0), (-1,-1), 0.4, TEAL),
("ROWBACKGROUNDS",(0,1), (-1,-1), [WHITE, LIGHT_BG]),
("BACKGROUND", (0,1), (0,-1), LIGHT_TL),
("BACKGROUND", (2,1), (2,-1), LIGHT_TL),
("FONTNAME", (0,1), (0,-1), "Helvetica-Bold"),
("FONTNAME", (2,1), (2,-1), "Helvetica-Bold"),
("FONTSIZE", (0,1), (0,-1), 7.5),
("FONTSIZE", (2,1), (2,-1), 7.5),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
story.append(kn_t)
story.append(sp(2))
# Footer note
story.append(HRFlowable(width=CW, thickness=1, color=TEAL))
story.append(sp(0.5))
story.append(Paragraph(
"<font color='#c0392b'><b>Exam Tip:</b></font> "
"Always state BOTH old and SI units when asked. Mention ALARA, inverse square law, "
"and rectangular collimation proactively in any radiation protection question. "
"PDL space widening is the EARLIEST sign in jaw osteosarcoma. "
"Rectangular collimation saves 60-70% dose compared to round.",
NOTE_S))
story.append(sp(0.3))
story.append(Paragraph(
"Prepared for BDS/MDS Oral Medicine & Oral Radiology Viva | Based on ICRP 2007 / NCRP / AERB / BDA Guidelines",
FOOT_S))
doc.build(story, onFirstPage=on_first_page, onLaterPages=on_page)
print("PDF built successfully:", path)
build()
cd /home/daytona/workspace/radiation-study && python3 build_pdf.py 2>&1
python3 -c "from reportlab.pdfgen import canvas; c=canvas.Canvas('/tmp/t.pdf'); …
~/radiation-study/build_pdf.py
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/radiation-study/build_pdf.py'
~/radiation-study/build_pdf.py
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/radiation-study/build_pdf.py'
cd /home/daytona/workspace/radiation-study && python3 build_pdf.py 2>&1
ls -lh /home/daytona/workspace/radiation-study/Radiation_Study_Guide.pdf
Radiation Study Guide
PDF Document · PDF
| Section | Content |
|---|---|
| 1. X-Ray Tube Diagram | Custom labelled schematic (cathode, filament, focusing cup, anode, beam) + full components table with materials and functions |
| 2. X-Ray Production | Bremsstrahlung vs Characteristic radiation comparison table |
| 3. Radiation Units | Complete old-unit → SI conversion table (R/C/kg, rad/Gy, rem/Sv, Ci/Bq) + Radiation Weighting Factors (Wr) + Tissue Weighting Factors (Wt) |
| 4. Dose Limits | ICRP 2007 occupational and public limits + typical dental radiograph doses (periapical, OPG, CBCT, etc.) |
| 5. Filtration | Filtration types table + beam quality gradient bar + collimation types comparison |
| 6. Inverse Square Law | Visual diagram with dot-density representation + formula box + 5 worked examples |
| 7. EM Spectrum | Colour-coded spectrum showing X-ray position + properties table with Duane-Hunt law |
| 8. Biological Effects | Stochastic vs deterministic table + radiosensitivity order (high/moderate/low) |
| 9. Beam Control Factors | How kVp, mA, time, filtration, distance each affect quality, quantity, dose, and image |
| 10. Radiation Protection | All operator rules, distances, angles, barriers, ALARA |
| 11. Mnemonics | SLOB rule, PIPE-SCIF, Bergonie & Tribondeau memory aid, quick conversion tricks |
| 12. Master Key-Numbers Card | 32 critical numbers in one reference table - the page to study the night before your viva |