@@ -44,7 +44,7 @@ class VmatOptimizationColGen(Optimization):
4444 def __init__ (self , my_plan : Plan , arcs = None , inf_matrix : InfluenceMatrix = None ,
4545 clinical_criteria : ClinicalCriteria = None ,
4646 opt_params : dict = None , vars : dict = None , sol = None ,
47- is_corr : bool = False , delta : np .ndarray = None ):
47+ is_corr : bool = False , delta : np .ndarray = None , predicted_dose = None ):
4848
4949 # combine steps objective functions into one
5050 if 'col_gen' in opt_params :
@@ -79,6 +79,7 @@ def __init__(self, my_plan: Plan, arcs=None, inf_matrix: InfluenceMatrix = None,
7979 self .constraints_ind_map = {}
8080 if delta is None :
8181 self .delta = np .zeros (self .inf_matrix .A .shape [0 ])
82+ self .predicted_dose = predicted_dose
8283
8384 def calculate_scores_grad_new (self , A , A_indices , sol ):
8485 """
@@ -107,6 +108,8 @@ def calculate_scores_grad_new(self, A, A_indices, sol):
107108
108109 # Initialize gradients
109110 num_beamlets = A .shape [1 ]
111+ ptv_dm_grad = np .zeros (num_beamlets )
112+ oar_dm_grad = np .zeros (num_beamlets )
110113 overdose_grad = np .zeros (num_beamlets )
111114 underdose_grad = np .zeros (num_beamlets )
112115 quadratic_grad = np .zeros (num_beamlets )
@@ -118,8 +121,33 @@ def calculate_scores_grad_new(self, A, A_indices, sol):
118121 inf_matrix = self .my_plan .inf_matrix
119122 num_fractions = self .my_plan .get_num_of_fractions ()
120123
124+ if self .predicted_dose is not None :
125+ pred_dose_per_frac = self .predicted_dose / num_fractions
126+
121127 for obj in obj_funcs :
122- if obj ['type' ] == 'quadratic-overdose' :
128+ if obj ['type' ] == "PTV-dose-mimicking" :
129+ struct = obj ['structure_name' ]
130+ if struct in self .my_plan .structures .get_structures ():
131+ vox = inf_matrix .get_opt_voxels_idx (struct )
132+ if len (vox ) == 0 :
133+ continue
134+ weight = float (obj ['weight' ])
135+ influence = A [vox , :]
136+ ptv_dm_grad += (2 * weight / len (vox )) * (influence .T @ (current_dose [vox ] - pred_dose_per_frac [vox ]))
137+ elif obj ['type' ] == "OAR-dose-mimicking" :
138+ target_voxels = self .inf_matrix .get_opt_voxels_idx ('PTV' )
139+ all_vox = self .my_plan .inf_matrix .get_opt_voxels_idx ('BODY' )
140+ oar_voxels = np .setdiff1d (all_vox , target_voxels )
141+ if oar_voxels .size == 0 :
142+ continue
143+ weight = obj ['weight' ]
144+ influence = A [oar_voxels , :]
145+ d_now = current_dose [oar_voxels ]
146+ d_hat = pred_dose_per_frac [oar_voxels ]
147+ over = d_now - d_hat
148+ over [over < 0.0 ] = 0.0
149+ oar_dm_grad += (2.0 * weight / len (oar_voxels )) * (influence .T @ over )
150+ elif obj ['type' ] == 'quadratic-overdose' :
123151 struct = obj ['structure_name' ]
124152 if struct not in structures .get_structures ():
125153 continue
@@ -567,10 +595,34 @@ def solve_rmp(self, *args, **kwargs):
567595 obj = self .obj
568596 constraints = self .constraints
569597
598+ if self .predicted_dose is not None :
599+ pred_dose_per_fraction = self .predicted_dose / num_fractions
600+
570601
571602 # obj += [(1 / B.shape[0]) * cp.sum_squares(cp.multiply(np.sqrt(self.weights), B @ y - p))]
572603 for i in range (len (obj_funcs )):
573- if obj_funcs [i ]['type' ] == 'quadratic-overdose' :
604+ if obj_funcs [i ]['type' ] == "PTV-dose-mimicking" :
605+ struct = obj_funcs [i ]['structure_name' ]
606+ if struct in self .my_plan .structures .get_structures ():
607+ vox = st .get_opt_voxels_idx (struct )
608+ if len (vox ) == 0 :
609+ continue
610+ weight = float (obj_funcs [i ]['weight' ])
611+ obj += [
612+ (weight / len (vox )) * cp .sum_squares (B [vox , :] @ y - pred_dose_per_fraction [vox ])
613+ ]
614+ elif obj_funcs [i ]['type' ] == "OAR-dose-mimicking" :
615+ target_voxels = self .inf_matrix .get_opt_voxels_idx ('PTV' )
616+ all_vox = self .my_plan .inf_matrix .get_opt_voxels_idx ('BODY' )
617+ oar_voxels = np .setdiff1d (all_vox , target_voxels )
618+ weight = float (obj_funcs [i ]['weight' ])
619+ dO = B [oar_voxels , :] @ y
620+ s_var = cp .Variable (len (oar_voxels ), nonneg = True )
621+ constraints += [ dO <= pred_dose_per_fraction [oar_voxels ] + s_var ]
622+ obj += [
623+ (weight / len (oar_voxels )) * cp .sum_squares (s_var )
624+ ]
625+ elif obj_funcs [i ]['type' ] == 'quadratic-overdose' :
574626 if obj_funcs [i ]['structure_name' ] in my_plan .structures .get_structures ():
575627 struct = obj_funcs [i ]['structure_name' ]
576628 if len (st .get_opt_voxels_idx (struct )) == 0 : # check if there are any opt voxels for the structure
0 commit comments