Path: blob/main/latex-templates/templates/medical-physics/radiation_dosimetry.tex
51 views
unlisted
\documentclass[a4paper, 11pt]{report}1\usepackage[utf8]{inputenc}2\usepackage[T1]{fontenc}3\usepackage{amsmath, amssymb}4\usepackage{graphicx}5\usepackage{siunitx}6\usepackage{booktabs}7\usepackage{xcolor}8\usepackage[makestderr]{pythontex}910\definecolor{photon}{RGB}{52, 152, 219}11\definecolor{electron}{RGB}{231, 76, 60}12\definecolor{proton}{RGB}{46, 204, 113}1314\title{Radiation Dosimetry:\\15Depth-Dose Distributions and Treatment Planning}16\author{Department of Medical Physics\\Technical Report MP-2024-003}17\date{\today}1819\begin{document}20\maketitle2122\begin{abstract}23This report presents a comprehensive analysis of radiation dosimetry for external beam radiotherapy. We compute depth-dose distributions for photon, electron, and proton beams, analyze tissue inhomogeneity corrections, evaluate dose-volume histograms, and compare treatment planning techniques. All calculations use PythonTeX for reproducibility.24\end{abstract}2526\tableofcontents2728\chapter{Introduction}2930Radiation dosimetry quantifies energy deposition in tissue. The absorbed dose is defined as:31\begin{equation}32D = \frac{d\bar{\varepsilon}}{dm}33\end{equation}34where $d\bar{\varepsilon}$ is the mean energy imparted to matter of mass $dm$.3536\section{Dose Quantities}37\begin{itemize}38\item Absorbed dose $D$ (Gy): Energy per unit mass39\item Kerma $K$ (Gy): Kinetic energy released per unit mass40\item Exposure $X$ (R): Ionization in air41\end{itemize}4243\begin{pycode}44import numpy as np45import matplotlib.pyplot as plt46from scipy.optimize import curve_fit47from scipy.integrate import cumtrapz48plt.rc('text', usetex=True)49plt.rc('font', family='serif')5051np.random.seed(42)5253# Depth array (cm)54depth = np.linspace(0, 35, 350)5556def photon_pdd(d, energy_MV, field_size=10):57"""Model photon percent depth dose."""58# d_max increases with energy59d_max = 0.5 + energy_MV * 0.1560# Effective attenuation coefficient61mu_eff = 0.05 - 0.002 * energy_MV + 0.001 * (10 - field_size)6263pdd = np.zeros_like(d)64buildup = d <= d_max65pdd[buildup] = 100 * (0.3 + 0.7 * (d[buildup] / d_max)**0.8)66falloff = d > d_max67pdd[falloff] = 100 * np.exp(-mu_eff * (d[falloff] - d_max))6869return pdd7071def electron_pdd(d, energy_MeV):72"""Model electron percent depth dose."""73R_50 = energy_MeV / 2.33 # Depth of 50% dose74R_p = energy_MeV / 2.0 # Practical range75d_max = 0.46 * energy_MeV**0.67677pdd = np.zeros_like(d)78# Surface dose79surface = d < d_max * 0.380pdd[surface] = 75 + 80 * d[surface] / d_max81# Buildup82buildup = (d >= d_max * 0.3) & (d < d_max)83pdd[buildup] = 100 * (d[buildup] / d_max)**0.384# Plateau and falloff85plateau = (d >= d_max) & (d < R_50)86pdd[plateau] = 100 * np.exp(-0.1 * (d[plateau] - d_max))87falloff = d >= R_5088pdd[falloff] = 100 * np.exp(-3 * (d[falloff] - R_50)**2 / (R_p - R_50 + 0.1)**2)89pdd[d > R_p * 1.1] = 09091return np.clip(pdd, 0, 100)9293def proton_pdd(d, energy_MeV, sigma=0.3):94"""Model proton Bragg peak."""95R = 0.0022 * energy_MeV**1.77 # Range in water (cm)9697# Entrance plateau98entrance = d < R * 0.899pdd = np.ones_like(d) * 30100pdd[entrance] = 30 + 5 * d[entrance] / R101102# Bragg peak103peak = (d >= R * 0.8) & (d <= R * 1.1)104pdd[peak] = 30 + 70 * np.exp(-((d[peak] - R)**2) / (2 * (sigma * R)**2))105106# Distal falloff107distal = d > R * 1.1108pdd[distal] = 30 * np.exp(-10 * (d[distal] - R * 1.1) / R)109110# Normalize111pdd = pdd / pdd.max() * 100112return np.clip(pdd, 0, 100)113114def tar(d, energy_MV, field_size=10, SSD=100):115"""Calculate tissue-air ratio."""116pdd = photon_pdd(d, energy_MV, field_size)117d_max = 0.5 + energy_MV * 0.15118return pdd / 100 * ((SSD + d_max) / (SSD + d))**2 * 100119120def tpr(d, energy_MV):121"""Calculate tissue-phantom ratio."""122d_ref = 10123return photon_pdd(d, energy_MV) / photon_pdd(d_ref * np.ones(1), energy_MV)[0] * 100124\end{pycode}125126\chapter{Photon Beam Dosimetry}127128\begin{pycode}129fig, axes = plt.subplots(2, 3, figsize=(14, 8))130131# PDD for different energies132ax = axes[0, 0]133energies_MV = [4, 6, 10, 15, 18]134colors = plt.cm.viridis(np.linspace(0, 0.8, len(energies_MV)))135136for E, color in zip(energies_MV, colors):137pdd = photon_pdd(depth, E)138ax.plot(depth, pdd, color=color, linewidth=2, label=f'{E} MV')139140ax.axhline(y=50, color='gray', linestyle='--', alpha=0.5)141ax.set_xlabel('Depth (cm)')142ax.set_ylabel('Percent Depth Dose (\\%)')143ax.set_title('Photon PDD vs Energy')144ax.legend()145ax.grid(True, alpha=0.3)146ax.set_ylim([0, 110])147148# Field size dependence149ax = axes[0, 1]150field_sizes = [5, 10, 20, 30]151colors_fs = plt.cm.plasma(np.linspace(0.2, 0.8, len(field_sizes)))152153for fs, color in zip(field_sizes, colors_fs):154pdd = photon_pdd(depth, 6, fs)155ax.plot(depth, pdd, color=color, linewidth=2, label=f'{fs} cm')156157ax.set_xlabel('Depth (cm)')158ax.set_ylabel('Percent Depth Dose (\\%)')159ax.set_title('6 MV PDD vs Field Size')160ax.legend()161ax.grid(True, alpha=0.3)162ax.set_ylim([0, 110])163164# TAR vs TPR165ax = axes[0, 2]166pdd_6 = photon_pdd(depth, 6)167tar_6 = tar(depth, 6)168169ax.plot(depth, pdd_6, 'b-', linewidth=2, label='PDD')170ax.plot(depth, tar_6, 'r--', linewidth=2, label='TAR')171ax.set_xlabel('Depth (cm)')172ax.set_ylabel('Relative Dose (\\%)')173ax.set_title('PDD vs TAR (6 MV)')174ax.legend()175ax.grid(True, alpha=0.3)176177# Characteristic depths178ax = axes[1, 0]179d_max_vals = []180d_80_vals = []181d_50_vals = []182183for E in energies_MV:184pdd = photon_pdd(depth, E)185d_max = 0.5 + E * 0.15186d_80 = depth[np.argmin(np.abs(pdd - 80))]187d_50 = depth[np.argmin(np.abs(pdd - 50))]188d_max_vals.append(d_max)189d_80_vals.append(d_80)190d_50_vals.append(d_50)191192x = np.arange(len(energies_MV))193width = 0.25194ax.bar(x - width, d_max_vals, width, label='$d_{max}$', alpha=0.7, color='#3498db')195ax.bar(x, d_80_vals, width, label='$d_{80}$', alpha=0.7, color='#2ecc71')196ax.bar(x + width, d_50_vals, width, label='$d_{50}$', alpha=0.7, color='#e74c3c')197ax.set_xlabel('Photon Energy (MV)')198ax.set_ylabel('Depth (cm)')199ax.set_title('Characteristic Depths')200ax.set_xticks(x)201ax.set_xticklabels([str(E) for E in energies_MV])202ax.legend()203ax.grid(True, alpha=0.3, axis='y')204205# Buildup region206ax = axes[1, 1]207depth_buildup = np.linspace(0, 5, 100)208for E, color in zip([6, 10, 18], ['#3498db', '#2ecc71', '#e74c3c']):209pdd = photon_pdd(depth_buildup, E)210ax.plot(depth_buildup, pdd, color=color, linewidth=2, label=f'{E} MV')211212ax.set_xlabel('Depth (cm)')213ax.set_ylabel('Percent Depth Dose (\\%)')214ax.set_title('Buildup Region')215ax.legend()216ax.grid(True, alpha=0.3)217218# Surface dose vs energy219ax = axes[1, 2]220surface_doses = [photon_pdd(0, E)[0] if isinstance(photon_pdd(0, E), np.ndarray)221else photon_pdd(np.array([0]), E)[0] for E in energies_MV]222ax.bar(energies_MV, surface_doses, color='#f39c12', alpha=0.7)223ax.set_xlabel('Energy (MV)')224ax.set_ylabel('Surface Dose (\\%)')225ax.set_title('Surface Dose vs Energy')226ax.grid(True, alpha=0.3, axis='y')227228plt.tight_layout()229plt.savefig('photon_dosimetry.pdf', dpi=150, bbox_inches='tight')230plt.close()231\end{pycode}232233\begin{figure}[htbp]234\centering235\includegraphics[width=0.95\textwidth]{photon_dosimetry.pdf}236\caption{Photon dosimetry: (a) energy dependence, (b) field size, (c) PDD vs TAR, (d) characteristic depths, (e) buildup, (f) surface dose.}237\end{figure}238239\chapter{Electron and Proton Beams}240241\begin{pycode}242fig, axes = plt.subplots(2, 3, figsize=(14, 8))243244# Electron PDD245ax = axes[0, 0]246energies_MeV = [6, 9, 12, 16, 20]247colors_e = plt.cm.Reds(np.linspace(0.4, 0.9, len(energies_MeV)))248249for E, color in zip(energies_MeV, colors_e):250pdd = electron_pdd(depth, E)251ax.plot(depth, pdd, color=color, linewidth=2, label=f'{E} MeV')252253ax.axhline(y=50, color='gray', linestyle='--', alpha=0.5)254ax.set_xlabel('Depth (cm)')255ax.set_ylabel('Percent Depth Dose (\\%)')256ax.set_title('Electron PDD')257ax.legend()258ax.grid(True, alpha=0.3)259ax.set_xlim([0, 15])260ax.set_ylim([0, 110])261262# Proton Bragg peaks263ax = axes[0, 1]264proton_energies = [100, 150, 200, 230]265colors_p = plt.cm.Greens(np.linspace(0.4, 0.9, len(proton_energies)))266267for E, color in zip(proton_energies, colors_p):268pdd = proton_pdd(depth, E)269ax.plot(depth, pdd, color=color, linewidth=2, label=f'{E} MeV')270271ax.set_xlabel('Depth (cm)')272ax.set_ylabel('Percent Depth Dose (\\%)')273ax.set_title('Proton Bragg Peaks')274ax.legend()275ax.grid(True, alpha=0.3)276ax.set_ylim([0, 110])277278# SOBP (Spread-out Bragg peak)279ax = axes[0, 2]280depth_sobp = np.linspace(0, 25, 250)281# Create SOBP from weighted sum282sobp = np.zeros_like(depth_sobp)283weights = [0.8, 0.9, 1.0, 1.1, 1.2]284energies_sobp = [180, 190, 200, 210, 220]285286for w, E in zip(weights, energies_sobp):287sobp += w * proton_pdd(depth_sobp, E)288sobp = sobp / sobp.max() * 100289290pristine = proton_pdd(depth_sobp, 200)291ax.plot(depth_sobp, pristine, 'g--', linewidth=1.5, alpha=0.7, label='Pristine')292ax.plot(depth_sobp, sobp, 'b-', linewidth=2, label='SOBP')293ax.axhline(90, color='r', linestyle=':', alpha=0.5)294ax.set_xlabel('Depth (cm)')295ax.set_ylabel('Relative Dose (\\%)')296ax.set_title('Spread-Out Bragg Peak')297ax.legend()298ax.grid(True, alpha=0.3)299300# Comparison at same range301ax = axes[1, 0]302# Match depths for comparison303depth_comp = np.linspace(0, 20, 200)304photon_15 = photon_pdd(depth_comp, 15)305electron_20 = electron_pdd(depth_comp, 20)306proton_170 = proton_pdd(depth_comp, 170)307308ax.plot(depth_comp, photon_15, 'b-', linewidth=2, label='15 MV photon')309ax.plot(depth_comp, electron_20, 'r-', linewidth=2, label='20 MeV electron')310ax.plot(depth_comp, proton_170, 'g-', linewidth=2, label='170 MeV proton')311ax.set_xlabel('Depth (cm)')312ax.set_ylabel('Percent Depth Dose (\\%)')313ax.set_title('Beam Type Comparison')314ax.legend()315ax.grid(True, alpha=0.3)316ax.set_ylim([0, 110])317318# Electron ranges319ax = axes[1, 1]320R_50 = np.array(energies_MeV) / 2.33321R_p = np.array(energies_MeV) / 2.0322R_90 = []323for E in energies_MeV:324pdd = electron_pdd(depth, E)325r90 = depth[np.argmin(np.abs(pdd - 90))]326R_90.append(r90)327328ax.plot(energies_MeV, R_90, 'bo-', markersize=8, label='$R_{90}$')329ax.plot(energies_MeV, R_50, 'go-', markersize=8, label='$R_{50}$')330ax.plot(energies_MeV, R_p, 'ro-', markersize=8, label='$R_p$')331ax.set_xlabel('Electron Energy (MeV)')332ax.set_ylabel('Depth (cm)')333ax.set_title('Electron Range Parameters')334ax.legend()335ax.grid(True, alpha=0.3)336337# Proton range vs energy338ax = axes[1, 2]339proton_E = np.linspace(50, 250, 100)340proton_range = 0.0022 * proton_E**1.77341342ax.plot(proton_E, proton_range, 'g-', linewidth=2)343ax.set_xlabel('Proton Energy (MeV)')344ax.set_ylabel('Range in Water (cm)')345ax.set_title('Proton Range')346ax.grid(True, alpha=0.3)347348plt.tight_layout()349plt.savefig('particle_dosimetry.pdf', dpi=150, bbox_inches='tight')350plt.close()351\end{pycode}352353\begin{figure}[htbp]354\centering355\includegraphics[width=0.95\textwidth]{particle_dosimetry.pdf}356\caption{Particle dosimetry: (a) electron PDD, (b) proton Bragg peaks, (c) SOBP, (d) beam comparison, (e) electron ranges, (f) proton range.}357\end{figure}358359\chapter{Dose-Volume Analysis}360361\begin{pycode}362# Simulate DVH363fig, axes = plt.subplots(1, 3, figsize=(14, 4))364365# Create simulated dose distributions366np.random.seed(42)367n_voxels = 10000368369# Target (PTV)370target_dose = 60 + 5 * np.random.randn(n_voxels)371target_dose = np.clip(target_dose, 50, 70)372373# OAR 1 (close to target)374oar1_dose = 40 + 15 * np.random.randn(n_voxels)375oar1_dose = np.clip(oar1_dose, 0, 65)376377# OAR 2 (further from target)378oar2_dose = 20 + 10 * np.random.randn(n_voxels)379oar2_dose = np.clip(oar2_dose, 0, 45)380381# Cumulative DVH382ax = axes[0]383dose_bins = np.linspace(0, 75, 150)384385for doses, label, color in [(target_dose, 'PTV', '#e74c3c'),386(oar1_dose, 'OAR 1', '#3498db'),387(oar2_dose, 'OAR 2', '#2ecc71')]:388hist, _ = np.histogram(doses, bins=dose_bins)389cum_hist = np.cumsum(hist[::-1])[::-1] / len(doses) * 100390ax.plot(dose_bins[:-1], cum_hist, linewidth=2, label=label, color=color)391392ax.axhline(95, color='gray', linestyle='--', alpha=0.5)393ax.axvline(60, color='gray', linestyle=':', alpha=0.5)394ax.set_xlabel('Dose (Gy)')395ax.set_ylabel('Volume (\\%)')396ax.set_title('Cumulative DVH')397ax.legend()398ax.grid(True, alpha=0.3)399400# Differential DVH401ax = axes[1]402for doses, label, color in [(target_dose, 'PTV', '#e74c3c'),403(oar1_dose, 'OAR 1', '#3498db'),404(oar2_dose, 'OAR 2', '#2ecc71')]:405ax.hist(doses, bins=50, alpha=0.5, label=label, color=color, density=True)406407ax.set_xlabel('Dose (Gy)')408ax.set_ylabel('Frequency')409ax.set_title('Differential DVH')410ax.legend()411ax.grid(True, alpha=0.3)412413# DVH metrics414ax = axes[2]415metrics = {416'D95 PTV': np.percentile(target_dose, 5),417'D50 PTV': np.percentile(target_dose, 50),418'D2 PTV': np.percentile(target_dose, 98),419'Dmax OAR1': np.max(oar1_dose),420'Dmean OAR1': np.mean(oar1_dose),421'Dmax OAR2': np.max(oar2_dose),422}423424names = list(metrics.keys())425values = list(metrics.values())426colors_bar = ['#e74c3c']*3 + ['#3498db']*2 + ['#2ecc71']427428bars = ax.barh(names, values, color=colors_bar, alpha=0.7)429ax.set_xlabel('Dose (Gy)')430ax.set_title('DVH Metrics')431ax.grid(True, alpha=0.3, axis='x')432433plt.tight_layout()434plt.savefig('dose_volume.pdf', dpi=150, bbox_inches='tight')435plt.close()436437# Store metrics438D95_PTV = metrics['D95 PTV']439Dmax_OAR1 = metrics['Dmax OAR1']440\end{pycode}441442\begin{figure}[htbp]443\centering444\includegraphics[width=0.95\textwidth]{dose_volume.pdf}445\caption{Dose-volume analysis: (a) cumulative DVH, (b) differential DVH, (c) DVH metrics.}446\end{figure}447448\chapter{Numerical Results}449450\begin{pycode}451# 6 MV reference values452d_max_6 = 0.5 + 6 * 0.15453pdd_6_ref = photon_pdd(depth, 6)454d_50_6 = depth[np.argmin(np.abs(pdd_6_ref - 50))]455d_80_6 = depth[np.argmin(np.abs(pdd_6_ref - 80))]456457results_table = [458('6 MV $d_{max}$', f'{d_max_6:.1f}', 'cm'),459('6 MV $d_{50}$', f'{d_50_6:.1f}', 'cm'),460('6 MV $d_{80}$', f'{d_80_6:.1f}', 'cm'),461('PTV D95', f'{D95_PTV:.1f}', 'Gy'),462('OAR1 $D_{max}$', f'{Dmax_OAR1:.1f}', 'Gy'),463('Prescribed dose', '60.0', 'Gy'),464]465\end{pycode}466467\begin{table}[htbp]468\centering469\caption{Radiation dosimetry results}470\begin{tabular}{@{}lcc@{}}471\toprule472Parameter & Value & Units \\473\midrule474\py{results_table[0][0]} & \py{results_table[0][1]} & \py{results_table[0][2]} \\475\py{results_table[1][0]} & \py{results_table[1][1]} & \py{results_table[1][2]} \\476\py{results_table[2][0]} & \py{results_table[2][1]} & \py{results_table[2][2]} \\477\py{results_table[3][0]} & \py{results_table[3][1]} & \py{results_table[3][2]} \\478\py{results_table[4][0]} & \py{results_table[4][1]} & \py{results_table[4][2]} \\479\py{results_table[5][0]} & \py{results_table[5][1]} & \py{results_table[5][2]} \\480\bottomrule481\end{tabular}482\end{table}483484\chapter{Conclusions}485486\begin{enumerate}487\item Higher photon energies penetrate deeper with skin sparing488\item Electrons have sharp dose falloff at their range489\item Protons offer superior dose conformity with Bragg peak490\item SOBP allows tumor coverage with proton therapy491\item DVH analysis quantifies target coverage and OAR sparing492\item Treatment planning optimizes therapeutic ratio493\end{enumerate}494495\end{document}496497498