Skip to content

Commit d83bc98

Browse files
committed
Updated to V1.0.4
1 parent 4306935 commit d83bc98

4 files changed

Lines changed: 142 additions & 21 deletions

File tree

examples/vmat_scp_tutorial.ipynb

Lines changed: 11 additions & 11 deletions
Large diffs are not rendered by default.

portpy/photon/influence_matrix.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -695,7 +695,7 @@ def get_bev_2d_grid(self, beam_id: Union[Union[int, str], List[Union[int, str]]]
695695
self.beamlets_dict[i]['beam_id'] == beam_id]
696696
if not ind:
697697
raise ValueError("invalid beam id {}".format(beam_id))
698-
if isinstance(ind, int) or isinstance(ind, str):
698+
elif isinstance(beam_id, list):
699699
for idx in beam_id:
700700
try:
701701
ind_1 = [i for i in range(len(self.beamlets_dict)) if

portpy/photon/vmat_scp/arcs.py

Lines changed: 91 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from portpy.photon.influence_matrix import InfluenceMatrix
44
import json
55
from copy import deepcopy
6+
from typing import Union, List
67

78
class Arcs:
89
"""
@@ -18,13 +19,23 @@ class Arcs:
1819
}
1920
2021
:type arcs_dict: dict
22+
:param inf_matrix: object of InfluenceMatrix class
23+
:type inf_matrix: object
24+
:param file_name: json file containing arcs information
25+
:type file_name: str
26+
:param data: object of DataExplorer class
27+
:type data: object
28+
2129
2230
- **Methods** ::
2331
24-
:get_gantry_angle(beam_id: Optional(int, List[int]):
25-
Get gantry angle in degrees
26-
:get_collimator_angle(beam_id):
27-
Get collimator angle in degrees
32+
method get_all_arcs: Get all arcs as a list
33+
method get_arc: Get arc based upon arc id
34+
method get_initial_leaf_pos: Get initial leaf position based upon BEV or other user defined criteria to start SCP
35+
method gen_interior_and_boundary_beamlets: Generate interior and boundary beamlets based upon step size
36+
method calc_actual_from_intermediate_sol: Calculate actual solution from intermediate solution
37+
38+
2839
2940
"""
3041

@@ -53,6 +64,32 @@ def load_json(self, arcs_json_file):
5364
f.close()
5465
return arcs_dict
5566

67+
def get_all_arcs(self):
68+
"""
69+
Get all arcs as a list
70+
71+
"""
72+
return self.arcs_dict['arcs']
73+
74+
def get_arc(self, arc_id: Union[Union[int, str], List[Union[int, str]]]):
75+
"""
76+
Get arc based upon arc id
77+
:param arc_id: arc id for the arc needed
78+
:return: list of arcs
79+
80+
"""
81+
ind = []
82+
if isinstance(arc_id, int) or isinstance(arc_id, str):
83+
ind = [i for i in range(len(self.arcs_dict['arcs'])) if
84+
self.arcs_dict['arcs'][i]['arc_id'] == arc_id]
85+
if not ind:
86+
raise ValueError("invalid arc id {}".format(arc_id))
87+
arcs = []
88+
for a in ind:
89+
arc = self.arcs_dict['arcs'][a]
90+
arcs.append(arc)
91+
return arcs
92+
5693
def get_max_cols(self):
5794
max_cols = 0
5895
arcs = self.arcs_dict['arcs']
@@ -73,11 +110,12 @@ def preprocess(self):
73110
arc['vmat_opt'] = beams_list
74111

75112
def get_initial_leaf_pos(self, initial_leaf_pos='BEV'):
76-
arcs_dict = self.arcs_dict
77113
"""
78-
Initialize leaf positions for the scp
114+
Initialize leaf positions for the scp.
115+
:param initial_leaf_pos: initial leaf position. Default is BEV
116+
:return: None
79117
"""
80-
118+
arcs_dict = self.arcs_dict
81119
for i, arc in enumerate((arcs_dict['arcs'])):
82120
beams_list = arc['vmat_opt']
83121
for j, beam in enumerate(beams_list):
@@ -119,6 +157,11 @@ def gen_interior_and_boundary_beamlets(self, forward_backward: int = 1, step_siz
119157
"""
120158
Create interior and boundary beamlets based upon step_size and forward backward
121159
160+
:param forward_backward: forward backward value. Default is 1. If 1, forward, if 0, backward
161+
:param step_size_f: step size for forward. Default is 8
162+
:param step_size_b: step size for backward. Default is 8
163+
:return: None
164+
122165
"""
123166
arcs_dict = self.arcs_dict
124167
for a, arc in enumerate(arcs_dict['arcs']):
@@ -210,7 +253,12 @@ def gen_interior_and_boundary_beamlets(self, forward_backward: int = 1, step_siz
210253
vmat[b]['int_ind'] = int_ind
211254

212255
def calc_actual_from_intermediate_sol(self, sol: dict):
256+
"""
257+
Create actual solution from intermediate solution.
258+
:param sol: solution dictionary
259+
:return: None
213260
261+
"""
214262
int_v = sol['int_v']
215263
bound_v_l = sol['bound_v_l']
216264
bound_v_r = sol['bound_v_r']
@@ -266,6 +314,10 @@ def update_leaf_pos(self, forward_backward: int, update_reference_leaf_pos: bool
266314
1] * (1 - forward_backward)
267315

268316
def update_best_solution(self):
317+
"""
318+
Update best solution if the current solution is better than the best solution.
319+
:return: None
320+
"""
269321
arcs = self.arcs_dict['arcs']
270322
for a, arc in enumerate(arcs):
271323
for b, beam in enumerate(arc['vmat_opt']):
@@ -274,6 +326,11 @@ def update_best_solution(self):
274326
arc['best_w_beamlet_act'] = arc['w_beamlet_act']
275327

276328
def calculate_beamlet_value(self):
329+
"""
330+
Calculate beamlet values between (0-1) for the intermediate solution.
331+
:return: None
332+
333+
"""
277334
# calculates the beamlet values between (0-1)
278335
arcs = self.arcs_dict['arcs']
279336
num_beamlets_so_far = 0
@@ -297,6 +354,16 @@ def calculate_beamlet_value(self):
297354
return arcs
298355

299356
def calculate_dose(self, inf_matrix: InfluenceMatrix, sol: dict, vmat_params: dict, best_plan: bool = False):
357+
"""
358+
359+
Calculate dose from the solution.
360+
:param inf_matrix: object of InfluenceMatrix class
361+
:param sol: solution dictionary
362+
:param vmat_params: vmat parameters
363+
:param best_plan: if True, calculate dose for best plan. Default is False
364+
:return: solution dictionary containing dose values
365+
366+
"""
300367
A = inf_matrix.A
301368
arcs = self.arcs_dict['arcs']
302369
adj1 = vmat_params['second_beam_adj']
@@ -339,6 +406,10 @@ def calculate_dose(self, inf_matrix: InfluenceMatrix, sol: dict, vmat_params: di
339406
return sol
340407

341408
def intermediate_to_actual(self):
409+
"""
410+
Convert intermediate solution to actual feasible solution.
411+
412+
"""
342413
arcs = self.arcs_dict['arcs']
343414
beamlet_so_far = 0
344415
# Convert intermediate solution to actual feasible solution
@@ -407,6 +478,13 @@ def intermediate_to_actual(self):
407478
beamlet_so_far = beamlet_so_far + num_beamlets
408479

409480
def _update_reference_leaf_pos(self):
481+
"""
482+
Update reference leaf position in forward and backward direction. The following leaf positions are updated only if a solution is accepted
483+
they are necessary because if a solution is rejected and forward/backward
484+
is changed then you need to go back to this reference leaf positions to
485+
update your leaf positions
486+
487+
"""
410488
arcs = self.arcs_dict['arcs']
411489
for arc in arcs:
412490

@@ -434,6 +512,12 @@ def _update_reference_leaf_pos(self):
434512
beam['leaf_pos_f'][r][1] = beam['leaf_pos_right'][r]
435513

436514
def _get_leaf_pos_in_beamlet(self, sol):
515+
"""
516+
Get leaf position relative to beamlets.
517+
:param sol: solution dictionary
518+
:return: None
519+
520+
"""
437521
arcs = self.arcs_dict['arcs']
438522
leaf_pos_mu_l = sol['leaf_pos_mu_l']
439523
leaf_pos_mu_r = sol['leaf_pos_mu_r']

portpy/photon/vmat_scp/vmat_scp_optimization.py

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,32 @@
1515

1616

1717
class VmatScpOptimization(Optimization):
18+
"""
19+
Class for VMAT optimization using Sequential Convex Programming (SCP) method
20+
21+
- **Attributes** ::
22+
:param my_plan: object of class Plan
23+
:param inf_matrix: object of class InfluenceMatrix
24+
:param clinical_criteria: object of class ClinicalCriteria
25+
:param opt_params: dictionary of vmat optimization parameters
26+
:param vars: dictionary of variables
27+
:param sol: Optional. solution to be passed for the optimization
28+
:param arcs: Optional. object of class Arcs
29+
30+
:Example:
31+
>>> vmat_opt = VmatScpOptimization(my_plan=my_plan, inf_matrix=inf_matrix, clinical_criteria=clinical_criteria, opt_params=vmat_opt_params)
32+
>>> vmat_opt.run_sequential_cvx_algo(solver='MOSEK', verbose=True)
33+
34+
- **Methods** ::
35+
:run_sequential_cvx_algo(solver: str, verbose: bool = False)
36+
Run Sequential Convex Programming algorithm for VMAT optimization
37+
:create_cvxpy_intermediate_problem()
38+
Creates cvxpy problem for ECHO
39+
:resolve_infeasibility_of_actual_solution(sol: dict, *args, **kwargs)
40+
Resolve infeasibility of the intermediate solution
41+
:create_cvxpy_actual_problem()
42+
Construct actual problem for optimizing MU
43+
"""
1844
def __init__(self, my_plan: Plan, inf_matrix: InfluenceMatrix = None,
1945
clinical_criteria: ClinicalCriteria = None,
2046
opt_params: dict = None, vars: dict = None, sol=None, arcs: Arcs = None):
@@ -41,7 +67,10 @@ def __init__(self, my_plan: Plan, inf_matrix: InfluenceMatrix = None,
4167

4268
def create_cvxpy_intermediate_problem(self):
4369
"""
44-
Creates cvxpy problem for ECHO
70+
71+
Creates intermediate cvxpy problem for optimizing interior and boundary beamlets
72+
:return: None
73+
4574
"""
4675
# unpack data
4776
my_plan = self.my_plan
@@ -108,6 +137,9 @@ def create_cvxpy_intermediate_problem(self):
108137
voxels_vol_cc = st.get_opt_voxels_volume_cc(struct)
109138
self.vars[dO] = cp.Variable(len(voxels), pos=True)
110139
obj += [(1 / cp.sum(voxels_vol_cc)) * (obj_funcs[i]['weight']*cp.sum_squares(cp.multiply(cp.sqrt(voxels_vol_cc), self.vars[dO])))]
140+
# inf_int is interior influence matrix, inf_bound_l is left boundary influence matrix, inf_bound_r is right boundary influence matrix
141+
# int_v is interior beamlet intensity, bound_v_l is left boundary beamlet intensity, bound_v_r is right boundary beamlet intensity
142+
# map_adj_int is mapping between interior variable and controlling MU for first and last beam due to inertia, map_adj_bound is similar
111143
constraints += [inf_int[voxels, :] @ cp.multiply(int_v, map_adj_int) + inf_bound_l[voxels, :] @ cp.multiply(bound_v_l, map_adj_bound)
112144
+ inf_bound_r[voxels, :] @ cp.multiply(bound_v_r, map_adj_bound) <= dose_gy + self.vars[dO]]
113145
print('Objective function type: {} , structure:{}, dose_gy:{}, weight:{} created..'.format(
@@ -410,6 +442,7 @@ def create_interior_and_boundary_inf_matrix(self):
410442
"""
411443
Create influence matrix based on interior and boundary beamlets
412444
445+
:return: inf_int, inf_bound_l, inf_bound_r
413446
"""
414447
print("Modifying influence matrix for boundary and interior beamlets. This process may take sometime..")
415448
A = self.inf_matrix.A
@@ -465,6 +498,8 @@ def create_cvx_params(self, actual_sol_correction: bool = False):
465498

466499
"""
467500
Create cvxpy related matrices for objective function and constraints
501+
502+
468503
"""
469504
if not actual_sol_correction:
470505
arcs = self.arcs.arcs_dict['arcs']
@@ -659,7 +694,9 @@ def calc_actual_objective_value(self, sol: dict, actual_sol_correction: bool = F
659694

660695
def run_sequential_cvx_algo(self, *args, **kwargs):
661696
"""
662-
Returns sol and convergence of the sequential convex algorithm for optimizing the plan
697+
Returns sol and convergence of the sequential convex algorithm for optimizing the plan.
698+
Solver parameters can be passed in args.
699+
663700
"""
664701
# running scp algorithm:
665702
inner_iteration = int(0)

0 commit comments

Comments
 (0)