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.
- pydosert-0.0.0/.github/workflows/publish.yml +30 -0
- pydosert-0.0.0/.github/workflows/python-package.yml +50 -0
- pydosert-0.0.0/.gitignore +29 -0
- pydosert-0.0.0/LICENSE +21 -0
- pydosert-0.0.0/PKG-INFO +480 -0
- pydosert-0.0.0/README.md +452 -0
- pydosert-0.0.0/example_data/water_patient.npz +0 -0
- pydosert-0.0.0/example_data/water_phantom.npz +0 -0
- pydosert-0.0.0/examples/calibration/calibration_1_penumbra.ipynb +159 -0
- pydosert-0.0.0/examples/calibration/calibration_2_head_scatter.ipynb +345 -0
- pydosert-0.0.0/examples/calibration/calibration_3_profile_correction.ipynb +291 -0
- pydosert-0.0.0/examples/calibration/calibration_4_output_factor.ipynb +156 -0
- pydosert-0.0.0/examples/calibration/calibration_5_dose_scaling.ipynb +164 -0
- pydosert-0.0.0/examples/direct_optimization.ipynb +255 -0
- pydosert-0.0.0/examples/kernels.ipynb +165 -0
- pydosert-0.0.0/examples/phantom.ipynb +248 -0
- pydosert-0.0.0/examples/rtplan_test_1arc.ipynb +220 -0
- pydosert-0.0.0/pyproject.toml +63 -0
- pydosert-0.0.0/scripts/compare_umea_plans.py +129 -0
- pydosert-0.0.0/scripts/dose_tests.py +91 -0
- pydosert-0.0.0/scripts/export_plan_test.py +53 -0
- pydosert-0.0.0/scripts/gold_atlas_test.py +146 -0
- pydosert-0.0.0/scripts/remote_script.sh +18 -0
- pydosert-0.0.0/scripts/rtplan_test_1arc.py +116 -0
- pydosert-0.0.0/scripts/rtplan_test_four_field.py +168 -0
- pydosert-0.0.0/scripts/rtplan_test_water_stuff.py +164 -0
- pydosert-0.0.0/scripts/run_remote.sh +5 -0
- pydosert-0.0.0/scripts/static_optimize_umea.py +356 -0
- pydosert-0.0.0/scripts/test_lots_of_beams.py +119 -0
- pydosert-0.0.0/scripts/vienna_test.py +144 -0
- pydosert-0.0.0/setup.cfg +4 -0
- pydosert-0.0.0/src/pydosert/__init__.py +16 -0
- pydosert-0.0.0/src/pydosert/data/__init__.py +13 -0
- pydosert-0.0.0/src/pydosert/data/beam.py +817 -0
- pydosert-0.0.0/src/pydosert/data/loaders.py +353 -0
- pydosert-0.0.0/src/pydosert/data/machine_config.py +132 -0
- pydosert-0.0.0/src/pydosert/data/machine_presets/lund-probe.json +10 -0
- pydosert-0.0.0/src/pydosert/data/machine_presets/test.json +4 -0
- pydosert-0.0.0/src/pydosert/data/machine_presets/umea_10MV.json +79 -0
- pydosert-0.0.0/src/pydosert/data/machine_presets/umea_15MV.json +13 -0
- pydosert-0.0.0/src/pydosert/data/machine_presets/umea_6MV.json +13 -0
- pydosert-0.0.0/src/pydosert/data/machine_presets/vienna_10MV.json +75 -0
- pydosert-0.0.0/src/pydosert/data/optimization_config.py +387 -0
- pydosert-0.0.0/src/pydosert/data/optimization_presets/gold-atlas.json +178 -0
- pydosert-0.0.0/src/pydosert/data/optimization_presets/lund-probe.json +68 -0
- pydosert-0.0.0/src/pydosert/data/optimization_presets/vienna.json +172 -0
- pydosert-0.0.0/src/pydosert/data/patient.py +202 -0
- pydosert-0.0.0/src/pydosert/data/utils/dicom_utils.py +205 -0
- pydosert-0.0.0/src/pydosert/engine/__init__.py +0 -0
- pydosert-0.0.0/src/pydosert/engine/dose_engine.py +586 -0
- pydosert-0.0.0/src/pydosert/geometry/projections.py +103 -0
- pydosert-0.0.0/src/pydosert/geometry/rotations.py +183 -0
- pydosert-0.0.0/src/pydosert/layers/BeamRotationLayer.py +104 -0
- pydosert-0.0.0/src/pydosert/layers/BeamValidationLayer.py +105 -0
- pydosert-0.0.0/src/pydosert/layers/BeamWiseConvolutionalLayer.py +81 -0
- pydosert-0.0.0/src/pydosert/layers/FluenceMapLayer.py +290 -0
- pydosert-0.0.0/src/pydosert/layers/FluenceVolumeLayer.py +206 -0
- pydosert-0.0.0/src/pydosert/layers/PencilBeamKernelLayer.py +87 -0
- pydosert-0.0.0/src/pydosert/layers/RadiologicalDepthLayer.py +224 -0
- pydosert-0.0.0/src/pydosert/layers/__init__.py +17 -0
- pydosert-0.0.0/src/pydosert/objectives/__init__.py +0 -0
- pydosert-0.0.0/src/pydosert/objectives/losses.py +693 -0
- pydosert-0.0.0/src/pydosert/objectives/metrics.py +431 -0
- pydosert-0.0.0/src/pydosert/physics/__init__.py +3 -0
- pydosert-0.0.0/src/pydosert/physics/attenuation/hu_density_conversion.py +53 -0
- pydosert-0.0.0/src/pydosert/physics/fluence/fluence_modeling.py +528 -0
- pydosert-0.0.0/src/pydosert/physics/kernels/pencil_beam_model.py +462 -0
- pydosert-0.0.0/src/pydosert/utils/__init__.py +0 -0
- pydosert-0.0.0/src/pydosert/utils/grad_monitor.py +91 -0
- pydosert-0.0.0/src/pydosert/utils/plotting.py +954 -0
- pydosert-0.0.0/src/pydosert/utils/utils.py +490 -0
- pydosert-0.0.0/src/pydosert.egg-info/PKG-INFO +480 -0
- pydosert-0.0.0/src/pydosert.egg-info/SOURCES.txt +94 -0
- pydosert-0.0.0/src/pydosert.egg-info/dependency_links.txt +1 -0
- pydosert-0.0.0/src/pydosert.egg-info/not-zip-safe +1 -0
- pydosert-0.0.0/src/pydosert.egg-info/requires.txt +19 -0
- pydosert-0.0.0/src/pydosert.egg-info/top_level.txt +1 -0
- pydosert-0.0.0/tests/benchmarks/test_benchmark_dose_engine.py +33 -0
- pydosert-0.0.0/tests/benchmarks/test_benchmark_fluence_map_layer.py +25 -0
- pydosert-0.0.0/tests/benchmarks/test_benchmark_fluence_volume_layer.py +24 -0
- pydosert-0.0.0/tests/benchmarks/test_benchmark_pencil_beam_kernel_layer.py +27 -0
- pydosert-0.0.0/tests/benchmarks/test_benchmark_radiological_depth_layer.py +30 -0
- pydosert-0.0.0/tests/conftest.py +169 -0
- pydosert-0.0.0/tests/smoketests/test_real_rtplan.py +57 -0
- pydosert-0.0.0/tests/unittests/test_beam.py +346 -0
- pydosert-0.0.0/tests/unittests/test_beam_rotation_layer.py +118 -0
- pydosert-0.0.0/tests/unittests/test_beam_validation_layer.py +149 -0
- pydosert-0.0.0/tests/unittests/test_beam_wise_convolutional_layer.py +170 -0
- pydosert-0.0.0/tests/unittests/test_dose_engine.py +355 -0
- pydosert-0.0.0/tests/unittests/test_fluence_map_layer.py +182 -0
- pydosert-0.0.0/tests/unittests/test_fluence_volume_layer.py +36 -0
- pydosert-0.0.0/tests/unittests/test_geometry.py +304 -0
- pydosert-0.0.0/tests/unittests/test_pencil_beam_kernel_layer.py +40 -0
- pydosert-0.0.0/tests/unittests/test_pencil_beam_kernel_model.py +202 -0
- pydosert-0.0.0/tests/unittests/test_radiological_depth_layer.py +36 -0
- 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.
|
pydosert-0.0.0/PKG-INFO
ADDED
|
@@ -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
|
+
[](https://www.python.org/downloads/)
|
|
34
|
+
[](LICENSE)
|
|
35
|
+
[](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.
|