Skip to content

Commit 283cea0

Browse files
committed
Bring few changes from Proton branch. Fix visualization.py and write_rt_plan_imrt.py
1 parent 75c3a11 commit 283cea0

File tree

7 files changed

+55
-37
lines changed

7 files changed

+55
-37
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<img src="./images/PortPy_logo.png" width="40%" height="40%">
44
</p>
55

6-
![Version](https://img.shields.io/static/v1?label=latest&message=v1.0.4.3&color=darkgreen)
6+
![Version](https://img.shields.io/static/v1?label=latest&message=v1.0.4.4&color=darkgreen)
77
[![Total Downloads](https://static.pepy.tech/personalized-badge/portpy?period=total&units=international_system&left_color=grey&right_color=blue&left_text=total%20downloads)](https://pepy.tech/project/portpy?&left_text=totalusers)
88
[![Monthly Downloads](https://static.pepy.tech/badge/portpy/month)](https://pepy.tech/project/portpy)
99
# What is PortPy?

portpy/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
__version__ = "1.0.4.3"
1+
__version__ = "1.0.4.4"
22

33
from portpy import photon

portpy/photon/clinical_criteria.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@ def get_dvh_table(self, my_plan: Plan, constraint_list: list = None, opt_params:
264264
df.at[count, 'dose_gy'] = self.dose_to_gy(dose_key, dvh_updated_list[i]['parameters'][dose_key])
265265
df.at[count, 'volume_perc'] = dvh_updated_list[i]['constraints'][goal_key]
266266
df.at[count, 'dvh_type'] = 'goal'
267+
df.at[count, 'weight'] = dvh_updated_list[i]['parameters']['weight']
267268
count = count + 1
268269
if 'dose_volume_D' in dvh_updated_list[i]['type']:
269270
limit_key = self.matching_keys(dvh_updated_list[i]['constraints'], 'limit')
@@ -279,6 +280,7 @@ def get_dvh_table(self, my_plan: Plan, constraint_list: list = None, opt_params:
279280
df.at[count, 'volume_perc'] = dvh_updated_list[i]['parameters']['volume_perc']
280281
df.at[count, 'dose_gy'] = self.dose_to_gy(goal_key, dvh_updated_list[i]['constraints'][goal_key])
281282
df.at[count, 'dvh_type'] = 'goal'
283+
df.at[count, 'weight'] = dvh_updated_list[i]['parameters']['weight']
282284
count = count + 1
283285
self.dvh_table = df
284286
self.get_max_tol(constraints_list=constraint_list)
@@ -301,7 +303,7 @@ def get_low_dose_vox_ind(self, my_plan: Plan, dose: np.ndarray):
301303
weights_sort = weights[sort_ind]
302304
weight_all_sum = np.sum(weights_sort)
303305
w_sum = 0
304-
if dvh_type == 'constraint':
306+
if dvh_type == 'constraint' or dvh_type == 'goal':
305307
for w_ind in range(n_struct_vox):
306308
w_sum = w_sum + weights_sort[w_ind]
307309
w_ratio = w_sum / weight_all_sum
@@ -320,12 +322,13 @@ def get_max_tol(self, constraints_list: list = None):
320322
dvh_table = self.dvh_table
321323
for ind in dvh_table.index:
322324
structure_name, dose_gy = dvh_table['structure_name'][ind], dvh_table['dose_gy'][ind]
323-
max_tol = 100
325+
max_tol = self.get_prescription() * 1.5 # Hard code. Temporary highest value of dose
324326
for criterion in constraints_list:
325327
if criterion['type'] == 'max_dose':
326328
if criterion['parameters']['structure_name'] == structure_name:
327-
key = self.matching_keys(criterion['constraints'], 'limit')
328-
max_tol = self.dose_to_gy(key, criterion['constraints'][key])
329+
limit_key = self.matching_keys(criterion['constraints'], 'limit')
330+
if limit_key:
331+
max_tol = self.dose_to_gy(limit_key, criterion['constraints'][limit_key])
329332
dvh_table.at[ind, 'max_tol'] = max_tol
330333

331334
return self.dvh_table

portpy/photon/data_explorer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@ def load_file(self, meta_data: dict):
388388
item = meta_data[key]
389389
if type(item) is dict:
390390
meta_data[key] = self.load_file(item)
391-
elif key == 'beamlets': # added this part to check if there are beamlets since beamlets are list of dictionary
391+
elif key == 'beamlets' or key == 'spots': # added this part to check if there are beamlets since beamlets are list of dictionary
392392
if type(item[0]) is dict:
393393
for ls in range(len(item)):
394394
self.load_file(item[ls])

portpy/photon/structures.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ def create_opt_structures(self, opt_params=None, clinical_criteria=None):
273273
result = mask_3d & self.get_structure_mask_3d('BODY')
274274
self.create_structure(new_struct_name=obj['structure_name'], mask_3d=result)
275275
except:
276-
Warning('Cannot evaluate structure defintion'.format(structure_def))
276+
print('Cannot evaluate structure defintion {}'.format(structure_def))
277277

278278
for ind, criterion in enumerate(constraints):
279279
if 'structure_def' in criterion['parameters']:
@@ -285,7 +285,7 @@ def create_opt_structures(self, opt_params=None, clinical_criteria=None):
285285
result = mask_3d & self.get_structure_mask_3d('BODY')
286286
self.create_structure(new_struct_name=param['structure_name'], mask_3d=result)
287287
except:
288-
Warning('Cannot evaluate structure defintion'.format(structure_def))
288+
print('Cannot evaluate structure defintion {}'.format(structure_def))
289289
print('Optimization structures created!!')
290290
# for param in rind_params:
291291
# self.set_opt_voxel_idx(struct_name=param['name'])

portpy/photon/utils/write_rt_plan_imrt.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@ def write_rt_plan_imrt(my_plan: Plan, leaf_sequencing: dict, out_rt_plan_file: s
1717
:param out_rt_plan_file: new rt plan which can be imported in TPS
1818
1919
"""
20-
# get positions. PortPy have same jaw settings for all beams
21-
top_left_x_mm = my_plan.beams.beams_dict['jaw_position'][0]['top_left_x_mm']
22-
bottom_right_x_mm = my_plan.beams.beams_dict['jaw_position'][0]['bottom_right_x_mm']
23-
bottom_right_y_mm = my_plan.beams.beams_dict['jaw_position'][0]['bottom_right_y_mm']
24-
top_left_y_mm = my_plan.beams.beams_dict['jaw_position'][0]['top_left_y_mm']
20+
# # get positions. PortPy have same jaw settings for all beams
21+
# top_left_x_mm = my_plan.beams.beams_dict['jaw_position'][0]['top_left_x_mm']
22+
# bottom_right_x_mm = my_plan.beams.beams_dict['jaw_position'][0]['bottom_right_x_mm']
23+
# bottom_right_y_mm = my_plan.beams.beams_dict['jaw_position'][0]['bottom_right_y_mm']
24+
# top_left_y_mm = my_plan.beams.beams_dict['jaw_position'][0]['top_left_y_mm']
2525

2626
# read rt plan file using pydicom
2727
ds = dcmread(in_rt_plan_file)
@@ -30,6 +30,12 @@ def write_rt_plan_imrt(my_plan: Plan, leaf_sequencing: dict, out_rt_plan_file: s
3030
meterset_weight = 0
3131
if gantry_angle in leaf_sequencing:
3232
leaf_postions = leaf_sequencing[gantry_angle]['leaf_postions']
33+
# get positions. PortPy have same jaw settings for all beams
34+
idx = my_plan.beams.beams_dict['gantry_angle'].index(gantry_angle)
35+
top_left_x_mm = my_plan.beams.beams_dict['jaw_position'][idx]['top_left_x_mm']
36+
bottom_right_x_mm = my_plan.beams.beams_dict['jaw_position'][idx]['bottom_right_x_mm']
37+
bottom_right_y_mm = my_plan.beams.beams_dict['jaw_position'][idx]['bottom_right_y_mm']
38+
top_left_y_mm = my_plan.beams.beams_dict['jaw_position'][idx]['top_left_y_mm']
3339
del ds.BeamSequence[b].ControlPointSequence[1:] # delete all the control point after 1st control point
3440
ds.BeamSequence[b].NumberOfControlPoints = len(leaf_postions) + 1
3541
for cp, shape in enumerate(leaf_postions):

portpy/photon/visualization.py

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ def plot_dvh(my_plan: Plan, sol: dict = None, dose_1d: np.ndarray = None, struct
8383
norm_flag = options['norm_flag'] if 'norm_flag' in options else False
8484
norm_volume = options['norm_volume'] if 'norm_volume' in options else 90
8585
norm_struct = options['norm_struct'] if 'norm_struct' in options else 'PTV'
86+
show_rx = options['show_rx'] if 'show_rx' in options else True
8687

8788
# plt.rcParams['font.size'] = font_size
8889
# plt.rc('font', family='serif')
@@ -106,15 +107,15 @@ def plot_dvh(my_plan: Plan, sol: dict = None, dose_1d: np.ndarray = None, struct
106107
norm_factor = Evaluation.get_dose(sol, dose_1d=dose_1d, struct=norm_struct, volume_per=norm_volume) / pres
107108
dose_1d = dose_1d / norm_factor
108109
count = 0
109-
for i in range(np.size(all_orgs)):
110-
if all_orgs[i] not in struct_names:
110+
for i in range(np.size(struct_names)):
111+
if struct_names[i] not in all_orgs:
111112
continue
112-
if my_plan.structures.get_fraction_of_vol_in_calc_box(all_orgs[i]) == 0: # check if the structure is within calc box
113-
print('Skipping Structure {} as it is not within calculation box.'.format(all_orgs[i]))
113+
if my_plan.structures.get_fraction_of_vol_in_calc_box(struct_names[i]) == 0: # check if the structure is within calc box
114+
print('Skipping Structure {} as it is not within calculation box.'.format(struct_names[i]))
114115
continue
115116
# for dose_1d in dose_list:
116117
#
117-
x, y = Evaluation.get_dvh(sol, struct=all_orgs[i], dose_1d=dose_1d)
118+
x, y = Evaluation.get_dvh(sol, struct=struct_names[i], dose_1d=dose_1d)
118119
if dose_scale == 'Absolute(Gy)':
119120
max_dose = np.maximum(max_dose, x[-1])
120121
ax.set_xlabel('Dose (Gy)', fontsize=fontsize)
@@ -129,10 +130,10 @@ def plot_dvh(my_plan: Plan, sol: dict = None, dose_1d: np.ndarray = None, struct
129130
ax.set_ylabel('Volume (cc)', fontsize=fontsize)
130131
elif volume_scale == 'Relative(%)':
131132
max_vol = np.maximum(max_vol, y[0] * 100)
132-
ax.set_ylabel('Volume Fraction ($\%$)', fontsize=fontsize)
133-
ax.plot(x, 100 * y, linestyle=style, linewidth=width, color=colors[count])
133+
ax.set_ylabel('Fractional Volume ($\%$)', fontsize=fontsize)
134+
ax.plot(x, 100 * y, linestyle=style, linewidth=width, color=colors[count], label=struct_names[i])
134135
count = count + 1
135-
legend.append(all_orgs[i])
136+
# legend.append(struct_names[i])
136137

137138
if show_criteria is not None:
138139
for s in range(len(show_criteria)):
@@ -146,23 +147,30 @@ def plot_dvh(my_plan: Plan, sol: dict = None, dose_1d: np.ndarray = None, struct
146147
final_xmax = max(current_xlim[1], max_dose * 1.1)
147148
ax.set_xlim(0, final_xmax)
148149
ax.set_ylim(0, max_vol)
149-
ax.legend(legend, prop={'size': legend_font_size}, loc=legend_loc)
150+
# ax.legend(legend, prop={'size': legend_font_size}, loc=legend_loc)
151+
handles, labels = ax.get_legend_handles_labels()
152+
# unique = [(h, l) for i, (h, l) in enumerate(zip(handles, labels)) if l not in labels[:i]]
153+
# Make all handles solid while ensuring unique legend entries
154+
unique = [(Line2D([], [], color=h.get_color(), linestyle='-', lw=h.get_linewidth()) if isinstance(h, Line2D) else h, l)
155+
for i, (h, l) in enumerate(zip(handles, labels)) if l not in labels[:i]]
156+
ax.legend(*zip(*unique), prop={'size': legend_font_size}, loc=legend_loc)
150157
ax.grid(visible=True, which='major', color='#666666', linestyle='-')
151158

152159
# Show the minor grid lines with very faint and almost transparent grey lines
153160
# plt.minorticks_on()
154161
ax.minorticks_on()
155-
plt.grid(visible=True, which='minor', color='#999999', linestyle='--', alpha=0.2)
156-
y = np.arange(0, 101)
157-
# if norm_flag:
158-
# x = pres * np.ones_like(y)
159-
# else:
160-
if dose_scale == "Absolute(Gy)":
161-
x = pres * np.ones_like(y)
162-
else:
163-
x = 100 * np.ones_like(y)
162+
plt.grid(visible=True, which='minor', color='#999999', linestyle='--', alpha=0.5)
163+
if show_rx:
164+
y = np.arange(0, 101)
165+
# if norm_flag:
166+
# x = pres * np.ones_like(y)
167+
# else:
168+
if dose_scale == "Absolute(Gy)":
169+
x = pres * np.ones_like(y)
170+
else:
171+
x = 100 * np.ones_like(y)
164172

165-
ax.plot(x, y, color='black')
173+
ax.plot(x, y, color='black')
166174
if title:
167175
ax.set_title(title)
168176
if show:
@@ -173,8 +181,8 @@ def plot_dvh(my_plan: Plan, sol: dict = None, dose_1d: np.ndarray = None, struct
173181

174182
@staticmethod
175183
def plot_robust_dvh(my_plan: Plan, sol: dict = None, dose_1d_list: list = None, struct_names: List[str] = None,
176-
dose_scale: dose_type = "Absolute(Gy)",
177-
volume_scale: volume_type = "Relative(%)", plot_scenario=None, **options):
184+
dose_scale: dose_type = "Absolute(Gy)",
185+
volume_scale: volume_type = "Relative(%)", plot_scenario=None, **options):
178186
"""
179187
Create dvh plot for the selected structures
180188
@@ -278,7 +286,7 @@ def plot_robust_dvh(my_plan: Plan, sol: dict = None, dose_1d_list: list = None,
278286
ax.set_xlabel('Dose (Gy)', fontsize=fontsize)
279287
elif dose_scale == 'Relative(%)':
280288
max_dose = np.maximum(max_dose, d_max_mat[-1])
281-
max_dose = max_dose/pres*100
289+
max_dose = max_dose / pres * 100
282290
ax.set_xlabel('Dose ($\%$)', fontsize=fontsize)
283291

284292
if volume_scale == 'Absolute(cc)':
@@ -287,7 +295,7 @@ def plot_robust_dvh(my_plan: Plan, sol: dict = None, dose_1d_list: list = None,
287295
ax.set_ylabel('Volume (cc)', fontsize=fontsize)
288296
elif volume_scale == 'Relative(%)':
289297
max_vol = np.maximum(max_vol, y[0] * 100)
290-
ax.set_ylabel('Volume Fraction ($\%$)', fontsize=fontsize)
298+
ax.set_ylabel('Fractional Volume ($\%$)', fontsize=fontsize)
291299
# ax.plot(x, 100 * y, linestyle=style, linewidth=width, color=colors[count])
292300

293301
# ax.plot(d_min_mat, 100 * y, linestyle='dotted', linewidth=width*0.5, color=colors[count])
@@ -453,6 +461,7 @@ def plot_2d_slice(my_plan: Plan = None, sol: dict = None, dose_1d: np.ndarray =
453461
454462
Plot 2d view of ct, dose_1d, isodose and struct_name contours
455463
464+
456465
:param my_plan: object of class Plan
457466
:param sol: Optional solution to optimization
458467
:param dose_1d: Optional dose as 1d array

0 commit comments

Comments
 (0)