Source code for portpy.photon.structures

import numpy as np
from copy import deepcopy


[docs]class Structures: """ A class representing structures. - **Attributes** :: :param structures_dict: structure dictionary that contains information about the structures present in the patient's CT scan. :type structures_dict: dict :param opt_voxels_dict: It contains information about optimization voxels in the form of dictionary :type opt_voxels_dict: dict - **Methods** :: :get_volume_cc(struct): Get volume in cc for the structure :expand(structure, margin_mm, new_structure): Expand the structure with margin_mm in mm :shrink(structure, margin_mm, new_structure): Shrink the structure with margin_mm in mm :union(str_1, str_2, str1_or_str2): Create union of two structures str_1 and str_2 :intersect(str_1, str_2, str1_and_str2): Create intersect of two structures str_1 and str_2 """ def __init__(self, structures: dict, opt_voxels: dict) -> None: """ :param structures: structures dictionary that contains information about the structures present in the patient's CT scan. :param opt_voxels: optimization voxels dictionary containing data about optimization voxels """ self.structures_dict = structures self.opt_voxels_dict = opt_voxels self.opt_voxels_dict['name'] = structures['name'] self._ct_voxel_resolution_xyz_mm = deepcopy(self.opt_voxels_dict['ct_voxel_resolution_xyz_mm']) self.preprocess_structures()
[docs] def get_structures(self) -> list: """ Returns all the structure names as list :return: """ return self.structures_dict['name']
[docs] def get_volume_cc(self, structure_name: str): """ Get volume in cc for the structure :param structure_name: name of the structure in plan :return: volume of the structure """ ind = self.structures_dict['name'].index(structure_name) return self.structures_dict['volume_cc'][ind]
[docs] def get_fraction_of_vol_in_calc_box(self, structure_name: str): """ Get fraction of volume in calc box for the structure :param structure_name: name of the structure in plan :return: volume of the structure """ ind = self.structures_dict['name'].index(structure_name) return self.structures_dict['fraction_of_vol_in_calc_box'][ind]
[docs] def preprocess_structures(self): """ preprocess structures to create optimization voxel indices for the structure :return: """ self.opt_voxels_dict['voxel_idx'] = [None] * len(self.structures_dict['name']) self.opt_voxels_dict['voxel_size'] = [None] * len(self.structures_dict['name']) for i in range(len(self.structures_dict['name'])): vox_3d = self.structures_dict['structure_mask_3d'][i] * self.opt_voxels_dict['ct_to_dose_voxel_map'][0] # my_plan.structures_dict['voxel_idx'][i] = np.unique(vox_3d[vox_3d > 0]) vox, counts = np.unique(vox_3d[vox_3d > 0], return_counts=True) self.opt_voxels_dict['voxel_idx'][i] = vox self.opt_voxels_dict['voxel_size'][i] = counts * np.prod(self._ct_voxel_resolution_xyz_mm) # calculate weight for each voxel
# self.opt_voxels_dict['voxel_size'][i] = counts / np.max(counts) # calculate weight for each voxel
[docs] def create_structure(self, new_structure: str, mask_3d: np.ndarray) -> None: """ Create a new structure and append its mask to the structures_dict :param new_structure: name of the new structure :param mask_3d: 3d mask for the structure :return: create new_structure """ for key in self.structures_dict.keys(): if key == 'name': self.structures_dict['name'].append(new_structure) elif key == 'structure_mask_3d': self.structures_dict['structure_mask_3d'].append(mask_3d) else: self.structures_dict[key].append(None)
[docs] def modify_structure(self, structure: str, mask_3d: np.ndarray): """ :param structure: name of the structure to be modified :param mask_3d: 3d mask for the structure :return: modify structure """ ind = self.structures_dict['name'].index(structure) for key in self.structures_dict.keys(): if key == 'name': self.structures_dict['name'][ind] = structure elif key == 'structure_mask_3d': self.structures_dict['structure_mask_3d'][ind] = mask_3d else: self.structures_dict[key][ind] = None
[docs] def delete_structure(self, structure: str): """ :param structure: structure to be removed :return: """ ind = self.structures_dict['name'].index(structure) for key in self.structures_dict.keys(): del self.structures_dict[key][ind]
[docs] def union(self, str_1: str, str_2: str, str1_or_str2: str) -> None: """ Create union of two structures str_1 and str_2. If str1_or_str2 is not in structures dict, it will create new structures. If str1_or_str2 is in structure_dict, it will modify the structure :param str_1: structure name for the 1st structure :param str_2: structure name for the 2nd structure :param str1_or_str2: structure name for the union of structure 1 and 2 :return: create union of the structures and save it to structures list """ if str1_or_str2 is None: raise Exception("str1_or_str2 need to be provided") ind1 = self.structures_dict['name'].index(str_1) ind2 = self.structures_dict['name'].index(str_2) mask_3d_1 = self.structures_dict['structure_mask_3d'][ind1] mask_3d_2 = self.structures_dict['structure_mask_3d'][ind2] new_mask_3d_1 = mask_3d_1 | mask_3d_2 if str1_or_str2 not in self.structures_dict['name']: self.create_structure(new_structure=str1_or_str2, mask_3d=new_mask_3d_1) else: self.modify_structure(structure=str1_or_str2, mask_3d=new_mask_3d_1)
[docs] def intersect(self, str_1: str, str_2: str, str1_and_str2: str) -> None: """ Create intersection of two structures str_1 and str_2. If str1_and_str2 is not in structures dict, it will create new structures. If str1_and_str2 is in structure_dict, it will modify the structure :param str_1: structure name for the 1st structure :param str_2: structure name for the 2nd structure :param str1_and_str2: structure name for the intersection of structure 1 and 2 :return: create intersection of the structures and save it to structures list """ if str1_and_str2 is None: raise Exception("str1_and_str2 need to be provided") ind1 = self.structures_dict['name'].index(str_1) ind2 = self.structures_dict['name'].index(str_2) mask_3d_1 = self.structures_dict['structure_mask_3d'][ind1] mask_3d_2 = self.structures_dict['structure_mask_3d'][ind2] new_mask_3d_1 = mask_3d_1 & mask_3d_2 if str1_and_str2 not in self.structures_dict['name']: self.create_structure(new_structure=str1_and_str2, mask_3d=new_mask_3d_1) else: self.modify_structure(structure=str1_and_str2, mask_3d=new_mask_3d_1)
[docs] def subtract(self, str_1: str, str_2: str, str1_sub_str2: str) -> None: """ :param str_1: structure name for the 1st structure :param str_2: structure name for the 2nd structure :param str1_sub_str2: structure name for subtracting 2 from 1 :return: create structure1 - structure2 and save it to list """ if str1_sub_str2 is None: raise Exception("str1_sub_str2 need to be provided") ind1 = self.structures_dict['name'].index(str_1) ind2 = self.structures_dict['name'].index(str_2) mask_3d_1 = self.structures_dict['structure_mask_3d'][ind1] mask_3d_2 = self.structures_dict['structure_mask_3d'][ind2] new_mask_3d_1 = mask_3d_1 - mask_3d_2 new_mask_3d_1[new_mask_3d_1 < 0] = np.uint8(0) if str1_sub_str2 not in self.structures_dict['name']: self.create_structure(new_structure=str1_sub_str2, mask_3d=new_mask_3d_1) else: self.modify_structure(structure=str1_sub_str2, mask_3d=new_mask_3d_1)
[docs] def expand(self, structure: str, margin_mm: float, new_structure: str) -> None: """ Expand the structure with the given margin_mm. :param structure: structure name to expand :param margin_mm: margin_mm in mm :param new_structure: new structure name. if same as structure name, expand the same structure. else create new structure :return: expand and save the structure mask in structures dictionary """ # from skimage import data, morphology, transform from scipy import ndimage ind = self.structures_dict['name'].index(structure) mask_3d = self.structures_dict['structure_mask_3d'][ind] # getting kernel size for expansion or shrinking if margin_mm > 0: num_voxels = np.round(margin_mm / np.asarray(self._ct_voxel_resolution_xyz_mm)).astype(int) kernel = ndimage.generate_binary_structure(rank=3, connectivity=2) # ball kernel # creating different iterations along z and xy directions num_iterations = int(num_voxels[0] / num_voxels[2]) iterations_in_one_step = int(np.round(num_voxels[0] / num_iterations)) # margin_mask_3d = ndimage.binary_dilation(mask_3d, structure=struct).astype(mask_3d.dtype) for i in range(num_iterations): if i == 0: margin_mask_3d = ndimage.binary_dilation(mask_3d, structure=kernel, iterations=iterations_in_one_step).astype(mask_3d.dtype) else: flat = np.copy(kernel) flat[0, :, :] = 0 flat[-1, :, :] = 0 margin_mask_3d = ndimage.binary_dilation(margin_mask_3d, structure=flat, iterations=iterations_in_one_step).astype(mask_3d.dtype) elif margin_mm == 0: margin_mask_3d = mask_3d else: raise ValueError('Invalid margin {}'.format(margin_mm)) if new_structure not in self.structures_dict['name']: self.create_structure(new_structure=new_structure, mask_3d=margin_mask_3d) else: self.modify_structure(structure=structure, mask_3d=margin_mask_3d)
[docs] def shrink(self, structure: str, margin_mm: float, new_structure: str) -> None: """ Shrink the structure with margin_mm. :param structure: structure name to shrink :param margin_mm: margin_mm in mm :param new_structure: new structure name. if same as structure, shrink the same structure. else create new structure :return: shrink and save the structure in structures_dictionary """ from scipy import ndimage ind = self.structures_dict['name'].index(structure) mask_3d = self.structures_dict['structure_mask_3d'][ind] # getting kernel size for expansion or shrinking num_voxels = np.round(margin_mm / np.asarray(self._ct_voxel_resolution_xyz_mm)).astype(int) struct = ndimage.generate_binary_structure(rank=3, connectivity=2) # ball kernel # creating different iterations along z and xy directions num_iterations = int(num_voxels[0] / num_voxels[2]) iterations_in_one_step = int(np.round(num_voxels[0] / num_iterations)) for i in range(num_iterations): if i == 0: margin_mask_3d = ndimage.binary_erosion(mask_3d, structure=struct, iterations=iterations_in_one_step).astype(mask_3d.dtype) else: flat = np.copy(struct) flat[0, :, :] = 0 flat[-1, :, :] = 0 margin_mask_3d = ndimage.binary_erosion(margin_mask_3d, structure=flat, iterations=iterations_in_one_step).astype(mask_3d.dtype) # margin_mask_3d = ndimage.binary_dilation(mask_3d, structure=structure).astype(mask_3d.dtype) if new_structure not in self.structures_dict['name']: self.create_structure(new_structure=new_structure, mask_3d=margin_mask_3d) else: self.modify_structure(structure=structure, mask_3d=margin_mask_3d)