codex/detector-volume-averaging-step.py

detector-volume-averaging-step.py
"""Uniform finite-detector response to an ideal radiation-field edge.

Run from the desired media output directory. Requires NumPy and Matplotlib
from the repository pyproject.toml; the PNG is written to the working directory.
The curves are analytic model calculations, not measurements.
"""
from pathlib import Path

import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import numpy as np


def averaged_step(position_mm, width_mm):
    """Average a unit step over a centred uniform interval of full width."""
    return np.clip(position_mm / width_mm + 0.5, 0.0, 1.0)


def main():
    position = np.linspace(-4.5, 4.5, 1201)
    fig, ax = plt.subplots(figsize=(7.2, 4.6), dpi=100, facecolor='white')
    ax.set_facecolor('white')
    ax.plot([-4.5, 0, 0, 4.5], [0, 0, 1, 1], color='black', ls='--',
            lw=1.8, label='Ideal point-sampled edge')
    ax.plot(position, averaged_step(position, 1), color='#c85b22', lw=2.2,
            label='Uniform detector width 1 mm')
    ax.plot(position, averaged_step(position, 4), color='#1766a4', lw=2.5,
            label='Uniform detector width 4 mm')
    ax.scatter([-1.2, 1.2], [0.2, 0.8], s=30, color='#1766a4', zorder=4)
    ax.hlines([0.2, 0.8], -4.5, [-1.2, 1.2], colors='#8b9baa',
              linestyles=':', linewidth=1)
    ax.vlines([-1.2, 1.2], -0.04, [0.2, 0.8], colors='#8b9baa',
              linestyles=':', linewidth=1)
    ax.annotate('', xy=(-1.2, 0.12), xytext=(1.2, 0.12),
                arrowprops={'arrowstyle': '<->', 'color': '#1766a4'})
    ax.text(0, 0.055, '20–80% width = 2.4 mm', color='#1766a4',
            ha='center', fontsize=10)
    ax.set(xlim=(-4.5, 4.5), ylim=(-0.04, 1.08),
           xlabel='Detector centre relative to ideal edge (mm)',
           ylabel='Normalized spatially averaged dose')
    ax.set_title('A finite detector broadens an ideal beam edge', fontsize=13)
    ax.grid(alpha=0.15)
    ax.legend(loc='upper left', fontsize=9, framealpha=1)
    fig.text(0.5, 0.018, 'Original uniform-response model; not measured data.',
             ha='center', fontsize=9, color='#555555')
    fig.tight_layout(rect=(0, 0.035, 1, 1))
    fig.savefig(Path('detector-volume-averaging-step.png'), dpi=100,
                facecolor='white', transparent=False)
    plt.close(fig)


if __name__ == '__main__':
    main()