Skip to content

Commit

Permalink
Bring few changes from Proton branch. Fix visualization.py and write_…
Browse files Browse the repository at this point in the history
…rt_plan_imrt.py
  • Loading branch information
gourav3017 committed Oct 30, 2024
1 parent 75c3a11 commit 283cea0
Show file tree
Hide file tree
Showing 7 changed files with 55 additions and 37 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<img src="./images/PortPy_logo.png" width="40%" height="40%">
</p>

![Version](https://img.shields.io/static/v1?label=latest&message=v1.0.4.3&color=darkgreen)
![Version](https://img.shields.io/static/v1?label=latest&message=v1.0.4.4&color=darkgreen)
[![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)
[![Monthly Downloads](https://static.pepy.tech/badge/portpy/month)](https://pepy.tech/project/portpy)
# What is PortPy?
Expand Down
2 changes: 1 addition & 1 deletion portpy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
__version__ = "1.0.4.3"
__version__ = "1.0.4.4"

from portpy import photon
11 changes: 7 additions & 4 deletions portpy/photon/clinical_criteria.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ def get_dvh_table(self, my_plan: Plan, constraint_list: list = None, opt_params:
df.at[count, 'dose_gy'] = self.dose_to_gy(dose_key, dvh_updated_list[i]['parameters'][dose_key])
df.at[count, 'volume_perc'] = dvh_updated_list[i]['constraints'][goal_key]
df.at[count, 'dvh_type'] = 'goal'
df.at[count, 'weight'] = dvh_updated_list[i]['parameters']['weight']
count = count + 1
if 'dose_volume_D' in dvh_updated_list[i]['type']:
limit_key = self.matching_keys(dvh_updated_list[i]['constraints'], 'limit')
Expand All @@ -279,6 +280,7 @@ def get_dvh_table(self, my_plan: Plan, constraint_list: list = None, opt_params:
df.at[count, 'volume_perc'] = dvh_updated_list[i]['parameters']['volume_perc']
df.at[count, 'dose_gy'] = self.dose_to_gy(goal_key, dvh_updated_list[i]['constraints'][goal_key])
df.at[count, 'dvh_type'] = 'goal'
df.at[count, 'weight'] = dvh_updated_list[i]['parameters']['weight']
count = count + 1
self.dvh_table = df
self.get_max_tol(constraints_list=constraint_list)
Expand All @@ -301,7 +303,7 @@ def get_low_dose_vox_ind(self, my_plan: Plan, dose: np.ndarray):
weights_sort = weights[sort_ind]
weight_all_sum = np.sum(weights_sort)
w_sum = 0
if dvh_type == 'constraint':
if dvh_type == 'constraint' or dvh_type == 'goal':
for w_ind in range(n_struct_vox):
w_sum = w_sum + weights_sort[w_ind]
w_ratio = w_sum / weight_all_sum
Expand All @@ -320,12 +322,13 @@ def get_max_tol(self, constraints_list: list = None):
dvh_table = self.dvh_table
for ind in dvh_table.index:
structure_name, dose_gy = dvh_table['structure_name'][ind], dvh_table['dose_gy'][ind]
max_tol = 100
max_tol = self.get_prescription() * 1.5 # Hard code. Temporary highest value of dose
for criterion in constraints_list:
if criterion['type'] == 'max_dose':
if criterion['parameters']['structure_name'] == structure_name:
key = self.matching_keys(criterion['constraints'], 'limit')
max_tol = self.dose_to_gy(key, criterion['constraints'][key])
limit_key = self.matching_keys(criterion['constraints'], 'limit')
if limit_key:
max_tol = self.dose_to_gy(limit_key, criterion['constraints'][limit_key])
dvh_table.at[ind, 'max_tol'] = max_tol

return self.dvh_table
2 changes: 1 addition & 1 deletion portpy/photon/data_explorer.py
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ def load_file(self, meta_data: dict):
item = meta_data[key]
if type(item) is dict:
meta_data[key] = self.load_file(item)
elif key == 'beamlets': # added this part to check if there are beamlets since beamlets are list of dictionary
elif key == 'beamlets' or key == 'spots': # added this part to check if there are beamlets since beamlets are list of dictionary
if type(item[0]) is dict:
for ls in range(len(item)):
self.load_file(item[ls])
Expand Down
4 changes: 2 additions & 2 deletions portpy/photon/structures.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ def create_opt_structures(self, opt_params=None, clinical_criteria=None):
result = mask_3d & self.get_structure_mask_3d('BODY')
self.create_structure(new_struct_name=obj['structure_name'], mask_3d=result)
except:
Warning('Cannot evaluate structure defintion'.format(structure_def))
print('Cannot evaluate structure defintion {}'.format(structure_def))

for ind, criterion in enumerate(constraints):
if 'structure_def' in criterion['parameters']:
Expand All @@ -285,7 +285,7 @@ def create_opt_structures(self, opt_params=None, clinical_criteria=None):
result = mask_3d & self.get_structure_mask_3d('BODY')
self.create_structure(new_struct_name=param['structure_name'], mask_3d=result)
except:
Warning('Cannot evaluate structure defintion'.format(structure_def))
print('Cannot evaluate structure defintion {}'.format(structure_def))
print('Optimization structures created!!')
# for param in rind_params:
# self.set_opt_voxel_idx(struct_name=param['name'])
Expand Down
16 changes: 11 additions & 5 deletions portpy/photon/utils/write_rt_plan_imrt.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ def write_rt_plan_imrt(my_plan: Plan, leaf_sequencing: dict, out_rt_plan_file: s
:param out_rt_plan_file: new rt plan which can be imported in TPS
"""
# get positions. PortPy have same jaw settings for all beams
top_left_x_mm = my_plan.beams.beams_dict['jaw_position'][0]['top_left_x_mm']
bottom_right_x_mm = my_plan.beams.beams_dict['jaw_position'][0]['bottom_right_x_mm']
bottom_right_y_mm = my_plan.beams.beams_dict['jaw_position'][0]['bottom_right_y_mm']
top_left_y_mm = my_plan.beams.beams_dict['jaw_position'][0]['top_left_y_mm']
# # get positions. PortPy have same jaw settings for all beams
# top_left_x_mm = my_plan.beams.beams_dict['jaw_position'][0]['top_left_x_mm']
# bottom_right_x_mm = my_plan.beams.beams_dict['jaw_position'][0]['bottom_right_x_mm']
# bottom_right_y_mm = my_plan.beams.beams_dict['jaw_position'][0]['bottom_right_y_mm']
# top_left_y_mm = my_plan.beams.beams_dict['jaw_position'][0]['top_left_y_mm']

# read rt plan file using pydicom
ds = dcmread(in_rt_plan_file)
Expand All @@ -30,6 +30,12 @@ def write_rt_plan_imrt(my_plan: Plan, leaf_sequencing: dict, out_rt_plan_file: s
meterset_weight = 0
if gantry_angle in leaf_sequencing:
leaf_postions = leaf_sequencing[gantry_angle]['leaf_postions']
# get positions. PortPy have same jaw settings for all beams
idx = my_plan.beams.beams_dict['gantry_angle'].index(gantry_angle)
top_left_x_mm = my_plan.beams.beams_dict['jaw_position'][idx]['top_left_x_mm']
bottom_right_x_mm = my_plan.beams.beams_dict['jaw_position'][idx]['bottom_right_x_mm']
bottom_right_y_mm = my_plan.beams.beams_dict['jaw_position'][idx]['bottom_right_y_mm']
top_left_y_mm = my_plan.beams.beams_dict['jaw_position'][idx]['top_left_y_mm']
del ds.BeamSequence[b].ControlPointSequence[1:] # delete all the control point after 1st control point
ds.BeamSequence[b].NumberOfControlPoints = len(leaf_postions) + 1
for cp, shape in enumerate(leaf_postions):
Expand Down
55 changes: 32 additions & 23 deletions portpy/photon/visualization.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ def plot_dvh(my_plan: Plan, sol: dict = None, dose_1d: np.ndarray = None, struct
norm_flag = options['norm_flag'] if 'norm_flag' in options else False
norm_volume = options['norm_volume'] if 'norm_volume' in options else 90
norm_struct = options['norm_struct'] if 'norm_struct' in options else 'PTV'
show_rx = options['show_rx'] if 'show_rx' in options else True

# plt.rcParams['font.size'] = font_size
# plt.rc('font', family='serif')
Expand All @@ -106,15 +107,15 @@ def plot_dvh(my_plan: Plan, sol: dict = None, dose_1d: np.ndarray = None, struct
norm_factor = Evaluation.get_dose(sol, dose_1d=dose_1d, struct=norm_struct, volume_per=norm_volume) / pres
dose_1d = dose_1d / norm_factor
count = 0
for i in range(np.size(all_orgs)):
if all_orgs[i] not in struct_names:
for i in range(np.size(struct_names)):
if struct_names[i] not in all_orgs:
continue
if my_plan.structures.get_fraction_of_vol_in_calc_box(all_orgs[i]) == 0: # check if the structure is within calc box
print('Skipping Structure {} as it is not within calculation box.'.format(all_orgs[i]))
if my_plan.structures.get_fraction_of_vol_in_calc_box(struct_names[i]) == 0: # check if the structure is within calc box
print('Skipping Structure {} as it is not within calculation box.'.format(struct_names[i]))
continue
# for dose_1d in dose_list:
#
x, y = Evaluation.get_dvh(sol, struct=all_orgs[i], dose_1d=dose_1d)
x, y = Evaluation.get_dvh(sol, struct=struct_names[i], dose_1d=dose_1d)
if dose_scale == 'Absolute(Gy)':
max_dose = np.maximum(max_dose, x[-1])
ax.set_xlabel('Dose (Gy)', fontsize=fontsize)
Expand All @@ -129,10 +130,10 @@ def plot_dvh(my_plan: Plan, sol: dict = None, dose_1d: np.ndarray = None, struct
ax.set_ylabel('Volume (cc)', fontsize=fontsize)
elif volume_scale == 'Relative(%)':
max_vol = np.maximum(max_vol, y[0] * 100)
ax.set_ylabel('Volume Fraction ($\%$)', fontsize=fontsize)
ax.plot(x, 100 * y, linestyle=style, linewidth=width, color=colors[count])
ax.set_ylabel('Fractional Volume ($\%$)', fontsize=fontsize)
ax.plot(x, 100 * y, linestyle=style, linewidth=width, color=colors[count], label=struct_names[i])
count = count + 1
legend.append(all_orgs[i])
# legend.append(struct_names[i])

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

# Show the minor grid lines with very faint and almost transparent grey lines
# plt.minorticks_on()
ax.minorticks_on()
plt.grid(visible=True, which='minor', color='#999999', linestyle='--', alpha=0.2)
y = np.arange(0, 101)
# if norm_flag:
# x = pres * np.ones_like(y)
# else:
if dose_scale == "Absolute(Gy)":
x = pres * np.ones_like(y)
else:
x = 100 * np.ones_like(y)
plt.grid(visible=True, which='minor', color='#999999', linestyle='--', alpha=0.5)
if show_rx:
y = np.arange(0, 101)
# if norm_flag:
# x = pres * np.ones_like(y)
# else:
if dose_scale == "Absolute(Gy)":
x = pres * np.ones_like(y)
else:
x = 100 * np.ones_like(y)

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

@staticmethod
def plot_robust_dvh(my_plan: Plan, sol: dict = None, dose_1d_list: list = None, struct_names: List[str] = None,
dose_scale: dose_type = "Absolute(Gy)",
volume_scale: volume_type = "Relative(%)", plot_scenario=None, **options):
dose_scale: dose_type = "Absolute(Gy)",
volume_scale: volume_type = "Relative(%)", plot_scenario=None, **options):
"""
Create dvh plot for the selected structures
Expand Down Expand Up @@ -278,7 +286,7 @@ def plot_robust_dvh(my_plan: Plan, sol: dict = None, dose_1d_list: list = None,
ax.set_xlabel('Dose (Gy)', fontsize=fontsize)
elif dose_scale == 'Relative(%)':
max_dose = np.maximum(max_dose, d_max_mat[-1])
max_dose = max_dose/pres*100
max_dose = max_dose / pres * 100
ax.set_xlabel('Dose ($\%$)', fontsize=fontsize)

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

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

0 comments on commit 283cea0

Please sign in to comment.