Skip to content

Commit f42d102

Browse files
committed
Changes to clinical_criteria and optimization
1 parent 218224f commit f42d102

File tree

2 files changed

+73
-75
lines changed

2 files changed

+73
-75
lines changed

portpy/photon/clinical_criteria.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,9 @@ def get_dvh_table(self, my_plan: Plan, constraint_list: list = None, opt_params:
267267
df = pd.DataFrame()
268268
count = 0
269269
for i in range(len(dvh_updated_list)):
270+
271+
dvh_method = dvh_updated_list[i]['parameters'].get('dvh_method', None)
272+
270273
if 'dose_volume_V' in dvh_updated_list[i]['type']:
271274
limit_key = self.matching_keys(dvh_updated_list[i]['constraints'], 'limit')
272275
dose_key = self.matching_keys(dvh_updated_list[i]['parameters'], 'dose_')
@@ -275,13 +278,15 @@ def get_dvh_table(self, my_plan: Plan, constraint_list: list = None, opt_params:
275278
df.at[count, 'dose_gy'] = self.dose_to_gy(dose_key, dvh_updated_list[i]['parameters'][dose_key])
276279
df.at[count, 'volume_perc'] = dvh_updated_list[i]['constraints'][limit_key]
277280
df.at[count, 'dvh_type'] = 'constraint'
281+
df.at[count, 'dvh_method'] = dvh_method
278282
count = count + 1
279283
goal_key = self.matching_keys(dvh_updated_list[i]['constraints'], 'goal')
280284
if goal_key in dvh_updated_list[i]['constraints']:
281285
df.at[count, 'structure_name'] = dvh_updated_list[i]['parameters']['structure_name']
282286
df.at[count, 'dose_gy'] = self.dose_to_gy(dose_key, dvh_updated_list[i]['parameters'][dose_key])
283287
df.at[count, 'volume_perc'] = dvh_updated_list[i]['constraints'][goal_key]
284288
df.at[count, 'dvh_type'] = 'goal'
289+
df.at[count, 'dvh_method'] = dvh_method
285290
df.at[count, 'weight'] = dvh_updated_list[i]['parameters']['weight']
286291
count = count + 1
287292
if 'dose_volume_D' in dvh_updated_list[i]['type']:
@@ -290,13 +295,15 @@ def get_dvh_table(self, my_plan: Plan, constraint_list: list = None, opt_params:
290295
df.at[count, 'structure_name'] = dvh_updated_list[i]['parameters']['structure_name']
291296
df.at[count, 'volume_perc'] = dvh_updated_list[i]['parameters']['volume_perc']
292297
df.at[count, 'dose_gy'] = self.dose_to_gy(limit_key, dvh_updated_list[i]['constraints'][limit_key])
298+
df.at[count, 'dvh_method'] = dvh_method
293299
df.at[count, 'dvh_type'] = 'constraint'
294300
count = count + 1
295301
goal_key = self.matching_keys(dvh_updated_list[i]['constraints'], 'goal')
296302
if goal_key in dvh_updated_list[i]['constraints']:
297303
df.at[count, 'structure_name'] = dvh_updated_list[i]['parameters']['structure_name']
298304
df.at[count, 'volume_perc'] = dvh_updated_list[i]['parameters']['volume_perc']
299305
df.at[count, 'dose_gy'] = self.dose_to_gy(goal_key, dvh_updated_list[i]['constraints'][goal_key])
306+
df.at[count, 'dvh_method'] = dvh_method
300307
df.at[count, 'dvh_type'] = 'goal'
301308
df.at[count, 'weight'] = dvh_updated_list[i]['parameters']['weight']
302309
count = count + 1

portpy/photon/optimization.py

Lines changed: 66 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,73 @@ def create_cvxpy_problem(self):
159159
d_max = np.infty * np.ones(A.shape[0]) # create a vector to avoid putting redundant max constraint on
160160
# duplicate voxels among structure
161161

162-
# Adding max/mean constraints
162+
# Adding constraints
163163
for i in range(len(constraint_def)):
164+
165+
item = constraint_def[i]
166+
item_type = item.get('type')
167+
168+
if item_type in ('dose_volume_V', 'dose_volume_D'):
169+
170+
params = item.get('parameters', {})
171+
cons = item.get('constraints', {})
172+
173+
dvh_method = params.get('dvh_method', None)
174+
if dvh_method != 'cvar':
175+
continue
176+
177+
struct = params['structure_name']
178+
voxel_idx = st.get_opt_voxels_idx(struct)
179+
if len(voxel_idx) == 0:
180+
continue
181+
182+
limit = float(params['dose_gy'])
183+
184+
volume_perc = float(cons['limit_volume_perc'])
185+
if not (0.0 < volume_perc < 100.0):
186+
raise ValueError(
187+
f"limit_volume_perc must be in (0,100), got {volume_perc}"
188+
)
189+
190+
alpha = 1.0 - volume_perc / 100.0
191+
dose_1d_list = A[voxel_idx, :] @ x * num_fractions
192+
193+
if cons.get('constraint_type') == 'upper':
194+
195+
label = f"constraint_{struct}_{item_type}_upper_a{alpha:.4f}"
196+
197+
zeta = cp.Variable(name=f"zeta_{label}")
198+
w = cp.Variable(len(voxel_idx), name=f"w_{label}")
199+
200+
self.vars[f"zeta_{label}"] = zeta
201+
self.vars[f"w_{label}"] = w
202+
203+
constraints += [
204+
zeta + (1.0 / ((1.0 - alpha) * len(voxel_idx))) * cp.sum(w) <= limit,
205+
w >= dose_1d_list - zeta,
206+
w >= 0
207+
]
208+
209+
elif cons.get('constraint_type') == 'lower':
210+
211+
dose_neg = -dose_1d_list
212+
limit_neg = -limit
213+
214+
label = f"constraint_{struct}_{item_type}_lower_a{alpha:.4f}"
215+
216+
zeta = cp.Variable(name=f"zeta_{label}")
217+
w = cp.Variable(len(voxel_idx), name=f"w_{label}")
218+
219+
self.vars[f"zeta_{label}"] = zeta
220+
self.vars[f"w_{label}"] = w
221+
222+
constraints += [
223+
zeta + (1.0 / ((1.0 - alpha) * len(voxel_idx))) * cp.sum(w) <= limit_neg,
224+
w >= dose_neg - zeta,
225+
w >= 0
226+
]
227+
228+
164229
if constraint_def[i]['type'] == 'max_dose':
165230
limit_key = self.matching_keys(constraint_def[i]['constraints'], 'limit')
166231
if limit_key:
@@ -188,48 +253,6 @@ def create_cvxpy_problem(self):
188253
(cp.sum((cp.multiply(st.get_opt_voxels_volume_cc(org),
189254
A[st.get_opt_voxels_idx(org), :] @ x))))
190255
<= limit / num_fractions]
191-
192-
elif constraint_def[i]['type'] == 'CVaR_Upper':
193-
194-
'''
195-
Json example:
196-
{
197-
"type": "CVaR_Upper",
198-
"parameters": {
199-
"structure_name": "HEART",
200-
"alpha": 0.90
201-
},
202-
"constraints": {
203-
"limit": 5
204-
}
205-
}
206-
'''
207-
208-
alpha = float(constraint_def[i]['parameters']['alpha'])
209-
if not (0.0 < alpha < 1.0):
210-
raise ValueError(f"CVaR_Upper: alpha must be in (0,1), got {alpha}")
211-
struct = constraint_def[i]['parameters']['structure_name']
212-
limit = constraint_def[i]['constraints']['limit']
213-
if struct in self.my_plan.structures.get_structures():
214-
voxel_idx = st.get_opt_voxels_idx(struct)
215-
if len(voxel_idx) > 0:
216-
dose_1d_list = A[voxel_idx, :] @ x * num_fractions # dose per voxel
217-
218-
label = f"constraint_{struct}_a{alpha:.4f}"
219-
220-
zeta = cp.Variable(name=f"zeta_{label}")
221-
w = cp.Variable(len(voxel_idx), name=f"w_{label}")
222-
223-
# Store variables in self.vars using label
224-
self.vars[f"zeta_{label}"] = zeta
225-
self.vars[f"w_{label}"] = w
226-
227-
# Add CVaR constraint
228-
constraints += [
229-
zeta + (1 / ((1 - alpha) * len(voxel_idx))) * cp.sum(w) <= limit,
230-
w >= dose_1d_list - zeta,
231-
w >= 0
232-
]
233256

234257
mask = np.isfinite(d_max)
235258
# Create index mask arrays
@@ -238,38 +261,6 @@ def create_cvxpy_problem(self):
238261
constraints += [A[all_d_max_vox_ind, :] @ x <= d_max[all_d_max_vox_ind]] # Add constraint for all d_max voxels at once
239262
print('Problem created')
240263

241-
def add_CVaR_Upper(self, alpha: float, limit: float, struct: str):
242-
"""
243-
add CVaR+ to the problem as a constraint
244-
245-
"""
246-
constraints = self.constraints
247-
x = self.vars["x"]
248-
st = self.inf_matrix
249-
A = self.inf_matrix.A
250-
num_fractions = self.clinical_criteria.get_num_of_fractions()
251-
252-
if struct in self.my_plan.structures.get_structures():
253-
voxel_idx = st.get_opt_voxels_idx(struct)
254-
255-
if len(voxel_idx) > 0:
256-
dose_1d_list = A[voxel_idx, :] @ x * num_fractions # dose per voxel
257-
258-
label = f"constraint_{struct}_a{alpha:.4f}"
259-
260-
zeta = cp.Variable(name=f"zeta_{label}")
261-
w = cp.Variable(len(voxel_idx), name=f"w_{label}")
262-
263-
# Store variables in self.vars using label
264-
self.vars[f"zeta_{label}"] = zeta
265-
self.vars[f"w_{label}"] = w
266-
267-
# Add CVaR constraint
268-
constraints += [
269-
zeta + (1 / ((1 - alpha) * len(voxel_idx))) * cp.sum(w) <= limit,
270-
w >= dose_1d_list - zeta,
271-
w >= 0
272-
]
273264

274265
def add_max(self, struct: str, dose_gy: float):
275266
"""

0 commit comments

Comments
 (0)