Skip to content

Commit 2bdd8cd

Browse files
committed
Merge branch 'feature/FREDinterface' into 'develop'
feature/FREDinterface See merge request e040/e0404/pyRadPlan!69
2 parents e0debd1 + 07af861 commit 2bdd8cd

File tree

22 files changed

+2183
-28
lines changed

22 files changed

+2183
-28
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,3 +173,5 @@ pyRadPlan/optimization/solvers/ipopt-0.1.9
173173
pyRadPlan/optimization/solvers/IpoptMUMPS
174174
.idea
175175
token.txt
176+
177+
Datasets/

.pre-commit-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
exclude: ^test/data/
12
repos:
23
- repo: https://github.com/pre-commit/pre-commit-hooks
34
rev: v2.3.0

examples/proton_MC_FRED.py

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
# %% [markdown]
2+
"""# Example for proton dose calculation using the FRED engine."""
3+
# %% [markdown]
4+
# This example demonstrates how to use the pyRadPlan library to perform proton dose calculations using the FRED Monte Carlo engine.
5+
6+
# For installation instructions, please refer to https://www.fred-mc.org/
7+
8+
# To display this script in a Jupyter Notebook, you need to install jupytext via pip and run the following command.
9+
# This will create a .ipynb file in the same directory:
10+
11+
# ```bash
12+
# pip install jupytext
13+
# jupytext --to notebook path/to/this/file/proton_MC_FRED.py
14+
15+
# %%
16+
# Import necessary libraries
17+
import logging
18+
import numpy as np
19+
20+
from pyRadPlan import (
21+
IonPlan,
22+
generate_stf,
23+
calc_dose_influence,
24+
calc_dose_forward,
25+
load_tg119,
26+
fluence_optimization,
27+
plot_slice,
28+
)
29+
30+
from pyRadPlan.optimization.objectives import SquaredDeviation, SquaredOverdosing, MeanDose
31+
32+
logging.basicConfig(level=logging.INFO)
33+
34+
# %%
35+
# Load TG119 (provided within pyRadPlan)
36+
ct, cst = load_tg119()
37+
38+
# %% [markdown]
39+
# In this section, we create a proton therapy plan using the FRED engine.
40+
# First, we define the parameters for the steering information (`prop_stf`).
41+
# Next, we specify the settings for dose calculation (`prop_dose_calc`),
42+
# including compatibility with different FRED versions through `dij_format_version`.
43+
# Finally, we configure the optimization parameters (`prop_opt`), as demonstrated in previous examples.
44+
45+
# Use less histories and beams when experimenting. This reduces computation time significantly.
46+
47+
# %%
48+
pln = IonPlan(radiation_mode="protons", machine="Generic")
49+
pln.prop_stf = {
50+
"gantry_angles": [0, 180], # define gantry angles for n beams
51+
"couch_angles": [0, 0],
52+
"longitudinal_spot_spacing": 2.0,
53+
"iso_center": np.array([[-5.0, -5.0, -5.0], [-5.0, -5.0, -5.0]]), # two beams
54+
"bixel_width": 10,
55+
"add_margin": 1,
56+
}
57+
pln.prop_dose_calc = {
58+
"engine": "FRED", # Use the FRED engine for dose calculation
59+
"fred_version": "3.76.0", # std: "3.70.0"
60+
"use_gpu": True, # std: True | one has to set GPU = 1 using 'fred -config' manually!
61+
"calc_let": False, # std: False
62+
"scorers": ["Dose"], # std: ["Dose"]
63+
"room_material": "Air", # vacuum | std: Air
64+
"print_output": False,
65+
"num_histories_per_beamlet": 2e3, # use less histories for faster computation (e.g. 2e2) or less beams
66+
# "dij_format_version": "31", # 20/30 for fred 3.70.0 and 21/31 for fred 3.76.0
67+
# "save_input": "path/to/save/input",
68+
# "save_output": "path/to/save/output", # or True (for current dir)/False | std: False (using temp path)
69+
# "use_output": "path/to/use/output", # skips output file generation if only 'use_output_file' is set and not 'save_output_file'
70+
}
71+
72+
pln.prop_opt = {"solver": "scipy"}
73+
74+
# %% [markdown]
75+
# Generate Steering Geometry ("stf")
76+
# %%
77+
stf = generate_stf(ct, cst, pln)
78+
79+
# %% [markdown]
80+
# Run calc_dose_influence (dose influence matrix)
81+
# %%
82+
dij = calc_dose_influence(ct, cst, stf, pln)
83+
84+
# %% [markdown]
85+
# Define cst objectives and run fluence optimization
86+
# %%
87+
cst.vois[0].objectives = [SquaredOverdosing(priority=100.0, d_max=1.0)] # OAR
88+
cst.vois[1].objectives = [SquaredDeviation(priority=1000.0, d_ref=3.0)] # Target
89+
cst.vois[2].objectives = [
90+
MeanDose(priority=1.0, d_ref=0.0),
91+
SquaredOverdosing(priority=10.0, d_max=2.0),
92+
] # BODY
93+
94+
fluence = fluence_optimization(ct, cst, stf, dij, pln)
95+
96+
# %% [markdown]
97+
# Compute dose distribution and other quantities given in pln.prop_opt
98+
# %%
99+
result_opt = dij.compute_result_ct_grid(fluence)
100+
101+
# %% [markdown]
102+
# Visualize the results
103+
# %%
104+
105+
# Choose a slice to visualize
106+
view_slice = int(np.round(ct.size[2] / 2))
107+
108+
plot_slice(
109+
ct=ct,
110+
cst=cst,
111+
overlay=result_opt["physical_dose"],
112+
view_slice=view_slice,
113+
plane="axial",
114+
overlay_unit="Gy",
115+
)
116+
117+
# %% [markdown]
118+
# You can also run calc_dose_forward (direct dose calculation) without optimization.
119+
# %%
120+
# Set run_direct_dose_calc to True to perform direct dose calculation
121+
run_direct_dose_calc = False
122+
if run_direct_dose_calc:
123+
result_fwd = calc_dose_forward(ct, cst, stf, pln, np.ones(stf.total_number_of_bixels))

pyRadPlan/dij/_dij.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,16 +173,18 @@ def validate_unique_indices_in_beam_num(
173173
)
174174
return v
175175

176-
@field_validator("beam_num", "ray_num", "bixel_num", mode="after")
176+
@field_validator("beam_num", "ray_num", "bixel_num", mode="before")
177177
@classmethod
178-
def validate_numbering_arrays(cls, v: np.ndarray, info: ValidationInfo) -> np.ndarray:
178+
def validate_numbering_arrays(cls, v: Any, info: ValidationInfo) -> np.ndarray:
179179
"""
180180
Validate the numbering arrays.
181181
182182
Raises
183183
------
184184
ValueError: inconsistent numbering arrays.
185185
"""
186+
if not isinstance(v, np.ndarray) and isinstance(v, int):
187+
v = np.array([v])
186188
# Check if the numbering arrays have the correct shape
187189
if info.data.get("physical_dose") is not None:
188190
dij_matrices = cast(np.ndarray, info.data["physical_dose"])

pyRadPlan/dose/engines/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,18 @@
33
from ._base import DoseEngineBase
44
from ._svdpb import PhotonPencilBeamSVDEngine
55
from ._hongpb import ParticleHongPencilBeamEngine
6-
6+
from ._fredmc import ParticleFredMCEngine
77
from ._factory import get_engine, get_available_engines, register_engine
88

99
register_engine(PhotonPencilBeamSVDEngine)
1010
register_engine(ParticleHongPencilBeamEngine)
11+
register_engine(ParticleFredMCEngine)
1112

1213
__all__ = [
1314
"DoseEngineBase",
1415
"PhotonPencilBeamSVDEngine",
1516
"ParticleHongPencilBeamEngine",
17+
"ParticleFredMCEngine",
1618
"get_engine",
1719
"get_available_engines",
1820
"register_engine",
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from ._base import DoseEngineBase
2+
3+
4+
class MonteCarloEngineAbstract(DoseEngineBase):
5+
def __init__(self, pln: dict):
6+
self.num_histories_per_beamlet: float = 2e2
7+
self.num_histories_direct: float = 1e6
8+
super().__init__(pln)

0 commit comments

Comments
 (0)