pydosert 0.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. pydosert-0.0.0/.github/workflows/publish.yml +30 -0
  2. pydosert-0.0.0/.github/workflows/python-package.yml +50 -0
  3. pydosert-0.0.0/.gitignore +29 -0
  4. pydosert-0.0.0/LICENSE +21 -0
  5. pydosert-0.0.0/PKG-INFO +480 -0
  6. pydosert-0.0.0/README.md +452 -0
  7. pydosert-0.0.0/example_data/water_patient.npz +0 -0
  8. pydosert-0.0.0/example_data/water_phantom.npz +0 -0
  9. pydosert-0.0.0/examples/calibration/calibration_1_penumbra.ipynb +159 -0
  10. pydosert-0.0.0/examples/calibration/calibration_2_head_scatter.ipynb +345 -0
  11. pydosert-0.0.0/examples/calibration/calibration_3_profile_correction.ipynb +291 -0
  12. pydosert-0.0.0/examples/calibration/calibration_4_output_factor.ipynb +156 -0
  13. pydosert-0.0.0/examples/calibration/calibration_5_dose_scaling.ipynb +164 -0
  14. pydosert-0.0.0/examples/direct_optimization.ipynb +255 -0
  15. pydosert-0.0.0/examples/kernels.ipynb +165 -0
  16. pydosert-0.0.0/examples/phantom.ipynb +248 -0
  17. pydosert-0.0.0/examples/rtplan_test_1arc.ipynb +220 -0
  18. pydosert-0.0.0/pyproject.toml +63 -0
  19. pydosert-0.0.0/scripts/compare_umea_plans.py +129 -0
  20. pydosert-0.0.0/scripts/dose_tests.py +91 -0
  21. pydosert-0.0.0/scripts/export_plan_test.py +53 -0
  22. pydosert-0.0.0/scripts/gold_atlas_test.py +146 -0
  23. pydosert-0.0.0/scripts/remote_script.sh +18 -0
  24. pydosert-0.0.0/scripts/rtplan_test_1arc.py +116 -0
  25. pydosert-0.0.0/scripts/rtplan_test_four_field.py +168 -0
  26. pydosert-0.0.0/scripts/rtplan_test_water_stuff.py +164 -0
  27. pydosert-0.0.0/scripts/run_remote.sh +5 -0
  28. pydosert-0.0.0/scripts/static_optimize_umea.py +356 -0
  29. pydosert-0.0.0/scripts/test_lots_of_beams.py +119 -0
  30. pydosert-0.0.0/scripts/vienna_test.py +144 -0
  31. pydosert-0.0.0/setup.cfg +4 -0
  32. pydosert-0.0.0/src/pydosert/__init__.py +16 -0
  33. pydosert-0.0.0/src/pydosert/data/__init__.py +13 -0
  34. pydosert-0.0.0/src/pydosert/data/beam.py +817 -0
  35. pydosert-0.0.0/src/pydosert/data/loaders.py +353 -0
  36. pydosert-0.0.0/src/pydosert/data/machine_config.py +132 -0
  37. pydosert-0.0.0/src/pydosert/data/machine_presets/lund-probe.json +10 -0
  38. pydosert-0.0.0/src/pydosert/data/machine_presets/test.json +4 -0
  39. pydosert-0.0.0/src/pydosert/data/machine_presets/umea_10MV.json +79 -0
  40. pydosert-0.0.0/src/pydosert/data/machine_presets/umea_15MV.json +13 -0
  41. pydosert-0.0.0/src/pydosert/data/machine_presets/umea_6MV.json +13 -0
  42. pydosert-0.0.0/src/pydosert/data/machine_presets/vienna_10MV.json +75 -0
  43. pydosert-0.0.0/src/pydosert/data/optimization_config.py +387 -0
  44. pydosert-0.0.0/src/pydosert/data/optimization_presets/gold-atlas.json +178 -0
  45. pydosert-0.0.0/src/pydosert/data/optimization_presets/lund-probe.json +68 -0
  46. pydosert-0.0.0/src/pydosert/data/optimization_presets/vienna.json +172 -0
  47. pydosert-0.0.0/src/pydosert/data/patient.py +202 -0
  48. pydosert-0.0.0/src/pydosert/data/utils/dicom_utils.py +205 -0
  49. pydosert-0.0.0/src/pydosert/engine/__init__.py +0 -0
  50. pydosert-0.0.0/src/pydosert/engine/dose_engine.py +586 -0
  51. pydosert-0.0.0/src/pydosert/geometry/projections.py +103 -0
  52. pydosert-0.0.0/src/pydosert/geometry/rotations.py +183 -0
  53. pydosert-0.0.0/src/pydosert/layers/BeamRotationLayer.py +104 -0
  54. pydosert-0.0.0/src/pydosert/layers/BeamValidationLayer.py +105 -0
  55. pydosert-0.0.0/src/pydosert/layers/BeamWiseConvolutionalLayer.py +81 -0
  56. pydosert-0.0.0/src/pydosert/layers/FluenceMapLayer.py +290 -0
  57. pydosert-0.0.0/src/pydosert/layers/FluenceVolumeLayer.py +206 -0
  58. pydosert-0.0.0/src/pydosert/layers/PencilBeamKernelLayer.py +87 -0
  59. pydosert-0.0.0/src/pydosert/layers/RadiologicalDepthLayer.py +224 -0
  60. pydosert-0.0.0/src/pydosert/layers/__init__.py +17 -0
  61. pydosert-0.0.0/src/pydosert/objectives/__init__.py +0 -0
  62. pydosert-0.0.0/src/pydosert/objectives/losses.py +693 -0
  63. pydosert-0.0.0/src/pydosert/objectives/metrics.py +431 -0
  64. pydosert-0.0.0/src/pydosert/physics/__init__.py +3 -0
  65. pydosert-0.0.0/src/pydosert/physics/attenuation/hu_density_conversion.py +53 -0
  66. pydosert-0.0.0/src/pydosert/physics/fluence/fluence_modeling.py +528 -0
  67. pydosert-0.0.0/src/pydosert/physics/kernels/pencil_beam_model.py +462 -0
  68. pydosert-0.0.0/src/pydosert/utils/__init__.py +0 -0
  69. pydosert-0.0.0/src/pydosert/utils/grad_monitor.py +91 -0
  70. pydosert-0.0.0/src/pydosert/utils/plotting.py +954 -0
  71. pydosert-0.0.0/src/pydosert/utils/utils.py +490 -0
  72. pydosert-0.0.0/src/pydosert.egg-info/PKG-INFO +480 -0
  73. pydosert-0.0.0/src/pydosert.egg-info/SOURCES.txt +94 -0
  74. pydosert-0.0.0/src/pydosert.egg-info/dependency_links.txt +1 -0
  75. pydosert-0.0.0/src/pydosert.egg-info/not-zip-safe +1 -0
  76. pydosert-0.0.0/src/pydosert.egg-info/requires.txt +19 -0
  77. pydosert-0.0.0/src/pydosert.egg-info/top_level.txt +1 -0
  78. pydosert-0.0.0/tests/benchmarks/test_benchmark_dose_engine.py +33 -0
  79. pydosert-0.0.0/tests/benchmarks/test_benchmark_fluence_map_layer.py +25 -0
  80. pydosert-0.0.0/tests/benchmarks/test_benchmark_fluence_volume_layer.py +24 -0
  81. pydosert-0.0.0/tests/benchmarks/test_benchmark_pencil_beam_kernel_layer.py +27 -0
  82. pydosert-0.0.0/tests/benchmarks/test_benchmark_radiological_depth_layer.py +30 -0
  83. pydosert-0.0.0/tests/conftest.py +169 -0
  84. pydosert-0.0.0/tests/smoketests/test_real_rtplan.py +57 -0
  85. pydosert-0.0.0/tests/unittests/test_beam.py +346 -0
  86. pydosert-0.0.0/tests/unittests/test_beam_rotation_layer.py +118 -0
  87. pydosert-0.0.0/tests/unittests/test_beam_validation_layer.py +149 -0
  88. pydosert-0.0.0/tests/unittests/test_beam_wise_convolutional_layer.py +170 -0
  89. pydosert-0.0.0/tests/unittests/test_dose_engine.py +355 -0
  90. pydosert-0.0.0/tests/unittests/test_fluence_map_layer.py +182 -0
  91. pydosert-0.0.0/tests/unittests/test_fluence_volume_layer.py +36 -0
  92. pydosert-0.0.0/tests/unittests/test_geometry.py +304 -0
  93. pydosert-0.0.0/tests/unittests/test_pencil_beam_kernel_layer.py +40 -0
  94. pydosert-0.0.0/tests/unittests/test_pencil_beam_kernel_model.py +202 -0
  95. pydosert-0.0.0/tests/unittests/test_radiological_depth_layer.py +36 -0
  96. pydosert-0.0.0/uv.lock +1529 -0
@@ -0,0 +1,30 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ permissions:
9
+ contents: read
10
+ id-token: write
11
+
12
+ jobs:
13
+ publish:
14
+ runs-on: ubuntu-latest
15
+ environment: pypi
16
+
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ with:
20
+ fetch-depth: 0
21
+
22
+ - uses: actions/setup-python@v5
23
+ with:
24
+ python-version: "3.12"
25
+
26
+ - run: |
27
+ python -m pip install --upgrade pip build
28
+ python -m build
29
+
30
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,50 @@
1
+ name: Publish Python distributions to Github release
2
+
3
+ on:
4
+ release:
5
+ types: [created]
6
+
7
+ permissions:
8
+ contents: write
9
+
10
+ env:
11
+ UV_SYSTEM_PYTHON: 1
12
+
13
+ jobs:
14
+ build-n-publish:
15
+ name: Publish Python distributions to Github release
16
+ runs-on: ubuntu-latest
17
+
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+
21
+ - name: Install uv
22
+ uses: astral-sh/setup-uv@v7
23
+ with:
24
+ # Install a specific version of uv.
25
+ version: "latest"
26
+
27
+ - name: Setup python
28
+ uses: actions/setup-python@v5
29
+ with:
30
+ python-version-file: pyproject.toml
31
+
32
+ - name: Install dependencies
33
+ run: uv pip install setuptools wheel twine
34
+
35
+ - name: Install package + test deps
36
+ run: uv pip install -e ".[test]"
37
+
38
+ - name: Run tests
39
+ run: pytest
40
+
41
+ - name: Build a binary wheel and a source tarball
42
+ run: uv build
43
+
44
+ - name: Release
45
+ uses: softprops/action-gh-release@v2
46
+ if: github.ref_type == 'tag'
47
+ with:
48
+ tag_name: ${{ github.event.release.tag_name }}
49
+ files: dist/pydosert*
50
+ token: ${{ secrets.RELEASE_TOKEN }}
@@ -0,0 +1,29 @@
1
+ **/__pycache__
2
+ *.idea
3
+ test_data/
4
+ .vscode/
5
+ out/
6
+ dist/
7
+ .env
8
+ .coverage
9
+ *.mat
10
+ *.zip
11
+ *.zip*
12
+ pwd.py
13
+ *.sif
14
+ *.gif
15
+ *.mp4
16
+ *.png
17
+ *.keras
18
+ *.dcm
19
+ readme.txt
20
+ *.pth
21
+ *.out
22
+ *.log
23
+ *.csv
24
+ *.csv#
25
+ *.npy
26
+ *egg-info
27
+ *.stats
28
+ commissioning/data/*
29
+ build/
pydosert-0.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Umeå University - Department of Diagnostics and Intervention
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,480 @@
1
+ Metadata-Version: 2.4
2
+ Name: pydosert
3
+ Version: 0.0.0
4
+ Summary: A differentiable radiation therapy dose calculation engine for automated treatment planning, built on PyTorch.
5
+ Author-email: Attila Simkó <gerd.heilemann@meduniwien.ac.at>, Gerd Heilemann <gerd.heilemann@meduniwien.ac.at>, Josef Lundman <josef.lundman@regionvasterbotten.se>
6
+ Requires-Python: <3.14,>=3.11
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: torch>=2.6.0
10
+ Requires-Dist: numpy>=1.26.4
11
+ Requires-Dist: scipy>=1.11.1
12
+ Requires-Dist: scikit-image>=0.25.2
13
+ Requires-Dist: pydantic>=2.11.0
14
+ Requires-Dist: pydantic-settings>=2.11.0
15
+ Requires-Dist: matplotlib>=3.10.0
16
+ Requires-Dist: imageio>=2.37.0
17
+ Requires-Dist: pydicom>=2.4.4
18
+ Requires-Dist: rt-utils>=1.2.7
19
+ Requires-Dist: SimpleITK>=2.4.1
20
+ Requires-Dist: pymedphys[tests]>=0.41.0
21
+ Requires-Dist: llvmlite>=0.45.1
22
+ Requires-Dist: numba>=0.62.1
23
+ Provides-Extra: test
24
+ Requires-Dist: pytest>=6.0.0; extra == "test"
25
+ Requires-Dist: pytest-benchmark; extra == "test"
26
+ Requires-Dist: pytest-cov>=6.2.1; extra == "test"
27
+ Dynamic: license-file
28
+
29
+ # PyDoseRT
30
+
31
+ A **differentiable radiation therapy dose calculation engine** for automated treatment planning, built on PyTorch.
32
+
33
+ [![Python](https://img.shields.io/badge/python-3.11%20%7C%203.12%20%7C%203.13-blue.svg)](https://www.python.org/downloads/)
34
+ [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
35
+ [![PyTorch](https://img.shields.io/badge/PyTorch-2.6%2B-red.svg)](https://pytorch.org/)
36
+
37
+ ## Overview
38
+
39
+ PyDoseRT implements a physics-based **pencil beam convolution model** with full gradient support, enabling gradient-based optimization of radiation therapy treatment plans. The engine is designed for researchers and medical physicists developing automated treatment planning algorithms.
40
+
41
+ ### Key Features
42
+
43
+ - **Fully Differentiable**: All operations support automatic differentiation for gradient-based optimization
44
+ - **Physics-Based Modeling**: Pencil beam convolution with tissue heterogeneity, scatter, and penumbra effects
45
+ - **DICOM Integration**: Native support for CT, RTDOSE, RTPLAN, and RTSTRUCT files
46
+ - Load existing treatment plans from TPS systems
47
+ - Import patient CT scans and structure sets
48
+ - Validate calculated dose against reference RTDOSE
49
+ - **GPU Accelerated**: CUDA-optimized computations for fast dose calculations
50
+ - Sequential processing mode for memory-efficient computation
51
+ - Parallel processing for maximum speed
52
+ - **Treatment Modalities**: Support for VMAT (Volumetric Modulated Arc Therapy), IMRT, and static fields
53
+ - **Clinical Validation**:
54
+ - Gamma index analysis (2%/2mm, 3%/3mm)
55
+ - DVH constraint evaluation
56
+ - Comparison with TPS dose distributions
57
+ - **Gradient-Based Optimization**: Optimize MLC leaf positions and monitor units directly
58
+ - **Calibration System**: Ensures accurate absolute dose at reference conditions
59
+
60
+ ## Installation
61
+
62
+ ### Requirements
63
+
64
+ - Python 3.11, 3.12, or 3.13
65
+ - CUDA-capable GPU (recommended, but CPU supported)
66
+ - Linux, macOS, or Windows
67
+
68
+ ### Install from Source
69
+
70
+ PyDoseRT is currently under active development. Install in editable mode to get the latest updates:
71
+
72
+ ```bash
73
+ # Clone the repository
74
+ git clone https://github.com/UMU-DDI/PyDoseRT.git
75
+ cd PyDoseRT
76
+
77
+ # Install in editable/development mode (recommended)
78
+ pip install -e .
79
+
80
+ # Or install with test dependencies
81
+ pip install -e ".[test]"
82
+ ```
83
+
84
+ The `-e` flag installs the package in editable mode, which means changes to the source code are immediately reflected without reinstalling. This is recommended for development and staying up-to-date with the latest improvements.
85
+
86
+ ### Dependencies
87
+
88
+ PyDoseRT requires the following key packages:
89
+ - **PyTorch** (≥2.6.0) - Deep learning framework and autodiff
90
+ - **NumPy** (≥1.26.4) - Numerical computing
91
+ - **SciPy** (≥1.11.1) - Scientific computing
92
+ - **pydicom** (≥2.4.4) - DICOM file handling
93
+ - **SimpleITK** (≥2.4.1) - Medical image processing
94
+ - **pymedphys** (≥0.41.0) - Medical physics utilities
95
+
96
+ See `pyproject.toml` for the complete dependency list.
97
+
98
+ ## Quick Start
99
+
100
+ ### Basic Dose Calculation from DICOM Data
101
+
102
+ ```python
103
+ import torch
104
+ from pydosert import DoseEngine
105
+ from pydosert.data import MachineConfig, loaders
106
+ from pydosert.data.beam import BeamSequence
107
+
108
+ # Setup device
109
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
110
+ dtype = torch.float32
111
+
112
+ # Load machine configuration (linear accelerator parameters)
113
+ machine_config = MachineConfig(
114
+ preset="src/pydosert/data/machine_presets/umea_10MV.json"
115
+ )
116
+
117
+ # Load patient DICOM data (CT, RTPLAN, RTDOSE, RTSTRUCT)
118
+ patient, beam_sequences = loaders.load_dicom(
119
+ ct_folder="path/to/ct_series/",
120
+ dose_path="path/to/rtdose.dcm",
121
+ plan_path="path/to/rtplan.dcm",
122
+ struct_path="path/to/rtstruct.dcm",
123
+ struct_names=["CTV", "PTV", "Bladder", "Rectum", "External"],
124
+ use_delivery=True # Use actual delivery MUs from plan
125
+ )
126
+
127
+ # Combine beam sequences if multiple arcs
128
+ beam_sequence = BeamSequence.from_beams(
129
+ [beam for bs in beam_sequences for beam in bs]
130
+ )
131
+
132
+ # Move data to device
133
+ patient = patient.to(device).to(dtype)
134
+ beam_sequence = beam_sequence.to(device).to(dtype)
135
+
136
+ # Get density image (masked by external contour)
137
+ density_image = torch.where(
138
+ patient.structures["External"],
139
+ patient.density_image,
140
+ 0.0
141
+ )
142
+
143
+ # Initialize dose engine
144
+ dose_engine = DoseEngine(
145
+ kernel_size=251, # Size of pencil beam kernel (larger = more accurate, slower)
146
+ dose_grid_spacing=patient.resolution, # Voxel spacing (mm)
147
+ dose_grid_shape=density_image.shape, # Grid dimensions
148
+ machine_config=machine_config,
149
+ beam_template=beam_sequence,
150
+ device=device,
151
+ dtype=dtype
152
+ )
153
+
154
+ # Calibrate dose engine to match machine output
155
+ dose_engine.calibrate(
156
+ calibration_mu=machine_config.calibration_mu,
157
+ original_beam_template=beam_sequence
158
+ )
159
+
160
+ # Calculate dose
161
+ dose_pred = dose_engine.compute_dose_sequential(
162
+ beam_sequence,
163
+ density_image=density_image
164
+ )
165
+
166
+ # Mask dose to external contour
167
+ dose_pred = torch.where(patient.structures["External"], dose_pred[0], 0.0)
168
+
169
+ # Visualize
170
+ from pydosert.utils.plotting import quick_plot
171
+ quick_plot(patient, dose_pred, title="Predicted Dose Distribution")
172
+ ```
173
+
174
+ ### Dose Validation and Metrics
175
+
176
+ ```python
177
+ from pydosert.objectives.metrics import result_validation
178
+ from pydosert.data import OptimizationConfig
179
+
180
+ # Load clinical objectives from preset
181
+ optimization = OptimizationConfig.from_json(
182
+ "src/pydosert/data/optimization_presets/gold-atlas.json"
183
+ )
184
+
185
+ # Validate calculated dose against reference
186
+ results = result_validation(
187
+ patient,
188
+ machine_config,
189
+ beam_sequence,
190
+ dose_pred,
191
+ optimization,
192
+ compute_gamma=True, # Gamma index analysis
193
+ compute_clinical_criteria=True, # DVH constraint checking
194
+ gamma_threshold_distance=2.0, # mm
195
+ gamma_threshold_dose=2.0 # %
196
+ )
197
+
198
+ # Print results
199
+ if "gamma_pass_rate" in results:
200
+ print(f"Gamma pass rate (2%/2mm): {results['gamma_pass_rate']:.2%}")
201
+
202
+ if "clinical_criteria" in results:
203
+ print(f"Clinical criteria passed: {results['clinical_criteria']['passed_test']:.1%}")
204
+
205
+ # Compare with TPS dose
206
+ import torch
207
+ mae = torch.abs(dose_pred - patient.dose).mean()
208
+ print(f"Mean Absolute Error: {mae:.3f} Gy")
209
+ ```
210
+
211
+ ### Treatment Plan Optimization
212
+
213
+ ```python
214
+ import torch
215
+ from pydosert.data import BeamSequence, OptimizationConfig
216
+ from pydosert.objectives.losses import compute_dvh_loss, scale_loss
217
+
218
+ # Load optimization objectives
219
+ optimization = OptimizationConfig.from_json(
220
+ "src/pydosert/data/optimization_presets/gold-atlas.json"
221
+ )
222
+
223
+ # Create optimizable beam sequence with gradient tracking
224
+ beam_sequence = BeamSequence.create(
225
+ gantry_angles_deg=[0, 51, 102, 153, 204, 255, 306], # 7 beams
226
+ number_of_leaf_pairs=60,
227
+ field_size=(400.0, 400.0), # mm
228
+ iso_center=(0.0, 0.0, 0.0),
229
+ collimator_angles_deg=[0.0] * 7,
230
+ sid=1000.0, # mm
231
+ open_field_size=100.0, # Initial aperture
232
+ device=device,
233
+ dtype=dtype,
234
+ requires_grad=True # Enable gradient tracking for MLC leaves and MUs
235
+ )
236
+
237
+ # Initialize optimizer (AdamW works well for fluence optimization)
238
+ optimizer = torch.optim.AdamW(
239
+ beam_sequence.parameters(),
240
+ lr=1.0,
241
+ weight_decay=1e-4
242
+ )
243
+
244
+ # Optimization loop
245
+ max_iterations = 100
246
+ for iteration in range(max_iterations):
247
+ optimizer.zero_grad()
248
+
249
+ # Forward pass: calculate dose with current beam parameters
250
+ dose_pred = dose_engine.compute_dose(
251
+ beam_sequence.to_delivery(),
252
+ density_image=patient.density_image.unsqueeze(0)
253
+ )
254
+
255
+ # Compute loss based on clinical constraints
256
+ losses = []
257
+
258
+ # PTV prescription (e.g., 60 Gy)
259
+ if "PTV" in patient.structures:
260
+ ptv_loss = torch.mean(
261
+ torch.abs(dose_pred[0][patient.structures["PTV"]] - 60.0)
262
+ )
263
+ losses.append(scale_loss(ptv_loss, optimization.structures["PTV"]["weight"]))
264
+
265
+ # OAR sparing (minimize dose to organs at risk)
266
+ for oar_name in ["Bladder", "Rectum", "FemoralHead_L", "FemoralHead_R"]:
267
+ if oar_name in patient.structures:
268
+ oar_loss = torch.mean(
269
+ torch.abs(dose_pred[0][patient.structures[oar_name]])
270
+ )
271
+ losses.append(scale_loss(oar_loss, optimization.structures[oar_name]["weight"]))
272
+
273
+ # Total loss
274
+ total_loss = torch.stack(losses).sum()
275
+
276
+ # Backward pass and optimization step
277
+ total_loss.backward()
278
+ optimizer.step()
279
+
280
+ if iteration % 10 == 0:
281
+ print(f"Iteration {iteration}: Loss = {total_loss.item():.4f}")
282
+ ```
283
+
284
+ ## Architecture
285
+
286
+ ### Dose Calculation Pipeline
287
+
288
+ PyDoseRT implements dose calculation as a series of differentiable PyTorch layers that process each beam's contribution:
289
+
290
+ 1. **Beam Validation Layer** - Validates beam geometry and MLC positions
291
+ 2. **Fluence Map Layer** - Converts MLC leaf positions and jaw settings to 2D fluence maps, accounting for:
292
+ - Leaf transmission
293
+ - Source penumbra (finite source size)
294
+ - Head scatter from collimators
295
+
296
+ 3. **Fluence Volume Layer** - Projects 2D fluence maps into 3D volumes using divergent beam geometry
297
+
298
+ 4. **Radiological Depth Layer** - Converts CT Hounsfield Units to radiological depth:
299
+ - HU-to-density conversion using calibrated lookup tables
300
+ - Ray-tracing through divergent beam geometry
301
+ - Effective depth calculation for tissue heterogeneity correction
302
+
303
+ 5. **Pencil Beam Kernel Layer** - Generates depth-dependent dose deposition kernels:
304
+ - Primary photon dose component
305
+ - Scatter dose with energy spectrum modeling
306
+ - Lateral scatter based on radiological depth
307
+ - Energy-dependent beam hardening
308
+
309
+ 6. **Beam-wise Convolution Layer** - Applies pencil beam kernels via 3D FFT convolution
310
+
311
+ 7. **Beam Rotation Layer** - Rotates dose distribution from beam's-eye-view to patient coordinates using trilinear interpolation
312
+
313
+ 8. **Accumulation** - Sums dose contributions from all control points/beams
314
+
315
+ ### Key Methods
316
+
317
+ The `DoseEngine` class provides several computation methods:
318
+
319
+ - **`compute_dose(beam_sequence, density_image)`** - Computes dose for a beam sequence in parallel (GPU memory intensive)
320
+ - **`compute_dose_sequential(beam_sequence, density_image)`** - Processes beams sequentially to reduce memory usage
321
+ - **`calibrate(calibration_mu, original_beam_template)`** - Calibrates the engine to match expected dose output at reference conditions
322
+
323
+ After initialization, the engine must be calibrated using a reference beam configuration to ensure accurate absolute dose values.
324
+
325
+ ### Repository Structure
326
+
327
+ ```
328
+ PyDoseRT/
329
+ ├── src/pydosert/ # Main source code
330
+ │ ├── engine/ # Core dose calculation engine
331
+ │ ├── data/ # Data structures and DICOM loaders
332
+ │ ├── layers/ # Computation layers (fluence, convolution, etc.)
333
+ │ ├── physics/ # Physics models (kernels, attenuation, scatter)
334
+ │ ├── geometry/ # Geometric transformations
335
+ │ ├── objectives/ # Loss functions and metrics
336
+ │ └── utils/ # Utilities and visualization
337
+ ├── examples/ # Jupyter notebook tutorials
338
+ ├── scripts/ # Command-line scripts
339
+ ├── tests/ # Test suite
340
+ │ ├── unittests/ # Unit tests
341
+ │ ├── benchmarks/ # Performance tests
342
+ │ └── smoketests/ # Integration tests
343
+ └── pyproject.toml # Package configuration
344
+ ```
345
+
346
+ ## Machine Configurations
347
+
348
+ PyDoseRT includes preset configurations for common linear accelerators:
349
+
350
+ TODO: Offer meaningful template
351
+ - **Generic configurations** - Customizable templates
352
+
353
+ You can create custom machine configurations by providing:
354
+ - MLC geometry (leaf widths, positions)
355
+ - Source characteristics (SSD, energy)
356
+ - Beam quality parameters (TPR 20/10)
357
+ - Collimation system parameters
358
+
359
+ ## Physics Model
360
+
361
+ ### Pencil Beam Convolution
362
+
363
+ The dose calculation uses a parameterized convolution method based on Nyholm et. al. 2006.
364
+
365
+ ```bibtex
366
+ @article{Nyholm2006,
367
+ title = {Photon pencil kernel parameterisation based on beam quality index},
368
+ author = {Tufve Nyholm and Jörgen Olofsson and Anders Ahnesjö and Mikael Karlsson},
369
+ doi = {10.1016/j.radonc.2006.02.002},
370
+ journal = {Radiotherapy and Oncology},
371
+ year = {2006}
372
+ }
373
+ ```
374
+
375
+ For a deeper understanding of the kernel computations, run `examples/kernel.ipynb`.
376
+
377
+ ### Tissue Heterogeneity
378
+
379
+ CT Hounsfield Units (HU) are converted to radiological depth using:
380
+ - Linear density-HU lookup tables
381
+ - Ray-tracing through divergent beam geometry
382
+ - Effective depth scaling for each beamlet
383
+
384
+ ### Additional Effects
385
+
386
+ - **MLC scatter and transmission** - Leaf leakage and interleaf effects
387
+ - **Head scatter** - Collimator-dependent scatter contribution
388
+ - **Source penumbra** - Geometric penumbra from finite source size
389
+ - **Tongue-and-groove effect** - MLC interdigitation
390
+
391
+ ## Examples
392
+
393
+ ### Jupyter Notebooks
394
+
395
+ Explore the `examples/` directory for interactive tutorials:
396
+
397
+ - **`phantom.ipynb`** - Basic dose calculations on water phantoms and simple geometries
398
+ - **`direct_optimization.ipynb`** - Treatment plan optimization workflows with gradient descent
399
+ - **`kernels.ipynb`** - Understanding pencil beam kernel computation and physics models
400
+ - **`rtplan_test_1arc.ipynb`** - Loading and validating DICOM RT plans (VMAT example)
401
+
402
+
403
+ ## Testing
404
+
405
+ Run the test suite:
406
+
407
+ ```bash
408
+ # Run all tests
409
+ pytest
410
+
411
+ # Run with coverage
412
+ pytest --cov=pydosert
413
+
414
+ # Run benchmarks
415
+ pytest tests/benchmarks/ --benchmark-only
416
+ ```
417
+
418
+ ## Performance
419
+
420
+ - **GPU Acceleration**: 10-100x speedup vs CPU for typical cases
421
+ - **Memory Efficiency**: Supports cropping to field-of-view and sequential beam processing
422
+ - **Mixed Precision**: FP16/FP32 support for memory-constrained scenarios
423
+ - **Batch Processing**: Multiple patients/beams in parallel
424
+
425
+ Typical performance (NVIDIA A100):
426
+
427
+ | Operation | Time [s] |
428
+ | ------------------------------------------- | -------- |
429
+ | Single beam dose calculation | 0.221 |
430
+ | VMAT prediction step (forward) | 2.051 |
431
+ | VMAT optimization step (forward + backward) | 5.102 |
432
+
433
+
434
+ ## Limitations
435
+
436
+ - **Pencil beam model**: Less accurate than Monte Carlo for high tissue heterogeneity
437
+ - **Photon therapy only**: Electron and proton therapy not currently supported
438
+ - **Simplified MLC model**: Does not include all vendor-specific details
439
+ - **Research tool**: Not clinically validated for treatment planning
440
+
441
+ ## Citation
442
+
443
+ If you use PyDoseRT in your research, please cite:
444
+
445
+ ```bibtex
446
+ @article{Simko2025,
447
+ title={A physics-informed, plug-and-play dose engine for gradient-based radiotherapy treatment planning},
448
+ author={Attila Simkó and Matthias Kronsteiner and Simon Glatzer and Minh Vu and Josef A. Lundman and Joakim Jonsson and Jörgen Olofsson and Kristina Sandgren and Wolfgang Lechner and Dietmar Georg and Tommy Löfstedt and Tufve Nyholm and Anders Garpebring and Gerd Heilemann},
449
+ year={2025},
450
+ url={https://arxiv.org/abs/2512.18863},
451
+ }
452
+ ```
453
+
454
+ PyDoseRT was developed in collaboration between Umeå University (Department of Diagnostics and Intervention) and the Medical University of Vienna (Department of Radiation Oncology).
455
+
456
+ ## Contributing
457
+
458
+ Contributions are welcome! Please:
459
+
460
+ 1. Fork the repository
461
+ 2. Create a feature branch (`git checkout -b feature/amazing-feature`)
462
+ 3. Make your changes with tests
463
+ 4. Run the test suite (`pytest`)
464
+ 5. Commit your changes (`git commit -m 'Add amazing feature'`)
465
+ 6. Push to the branch (`git push origin feature/amazing-feature`)
466
+ 7. Open a Pull Request
467
+
468
+ ## License
469
+
470
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
471
+
472
+ ## Support
473
+
474
+ For questions, issues, or feature requests:
475
+ - Open an issue on [GitHub](https://github.com/UMU-DDI/PyDoseRT/issues)
476
+ - Contact the authors via [email](attila.simko@umu.se)
477
+
478
+ ---
479
+
480
+ **Disclaimer**: PyDoseRT is a research tool and has not been clinically validated. It should not be used for clinical treatment planning without proper validation and regulatory approval.