def save_new_rtss_to_moving_image_set(self, event=None): """ Save the current RTSS stored in moving patient dictionary to the file system. ROIs modification into moving patient dict is auto saved :param event: Not used but will be passed as an argument from modified_indicator_widget on mouseReleaseEvent """ if self.moving_dict_container.get("existing_file_rtss") is not None: existing_rtss_directory = str( Path(self.moving_dict_container.get("existing_file_rtss"))) else: existing_rtss_directory = None rtss_directory = str(Path(self.moving_dict_container.get("file_rtss"))) if existing_rtss_directory is None: self.moving_dict_container.get("dataset_rtss").save_as( rtss_directory) else: new_rtss = self.moving_dict_container.get("dataset_rtss") old_rtss = pydicom.dcmread(existing_rtss_directory, force=True) old_roi_names = \ set(value["name"] for value in ImageLoading.get_roi_info(old_rtss).values()) new_roi_names = \ set(value["name"] for value in self.moving_dict_container.get("rois").values()) duplicated_names = old_roi_names.intersection(new_roi_names) merged_rtss = merge_rtss(old_rtss, new_rtss, duplicated_names) merged_rtss.save_as(existing_rtss_directory) self.moving_dict_container.set("rtss_modified", False)
def __init__(self): # Load test DICOM files desired_path = Path.cwd().joinpath('test', 'testdata') selected_files = find_DICOM_files(desired_path) # list of DICOM test files file_path = os.path.dirname(os.path.commonprefix(selected_files)) # file path of DICOM files read_data_dict, file_names_dict = ImageLoading.get_datasets(selected_files) # Create patient dict container object patient_dict_container = PatientDictContainer() patient_dict_container.clear() patient_dict_container.set_initial_values(file_path, read_data_dict, file_names_dict) # Set additional attributes in patient dict container (otherwise program will crash and test will fail) if "rtss" in file_names_dict: dataset_rtss = dcmread(file_names_dict['rtss']) self.rois = ImageLoading.get_roi_info(dataset_rtss) dict_raw_contour_data, dict_numpoints = ImageLoading.get_raw_contour_data(dataset_rtss) dict_pixluts = ImageLoading.get_pixluts(read_data_dict) patient_dict_container.set("rois", self.rois) patient_dict_container.set("raw_contour", dict_raw_contour_data) patient_dict_container.set("num_points", dict_numpoints) patient_dict_container.set("pixluts", dict_pixluts) # Open the main window self.main_window = MainWindow()
def create_new_rtstruct(cls, progress_callback): """ Generates a new RTSS and edits the patient dict container. Used for batch processing. """ # Get common directory patient_dict_container = PatientDictContainer() file_path = patient_dict_container.filepaths.values() file_path = Path(os.path.commonpath(file_path)) # Get new RT Struct file path file_path = str(file_path.joinpath("rtss.dcm")) # Create RT Struct file progress_callback.emit(("Generating RT Structure Set", 60)) ct_uid_list = ImageLoading.get_image_uid_list( patient_dict_container.dataset) ds = ROI.create_initial_rtss_from_ct(patient_dict_container.dataset[0], file_path, ct_uid_list) ds.save_as(file_path) # Add RT Struct file path to patient dict container patient_dict_container.filepaths['rtss'] = file_path filepaths = patient_dict_container.filepaths # Add RT Struct dataset to patient dict container patient_dict_container.dataset['rtss'] = ds dataset = patient_dict_container.dataset # Set some patient dict container attributes patient_dict_container.set("file_rtss", filepaths['rtss']) patient_dict_container.set("dataset_rtss", dataset['rtss']) dicom_tree_rtss = DicomTree(filepaths['rtss']) patient_dict_container.set("dict_dicom_tree_rtss", dicom_tree_rtss.dict) dict_pixluts = ImageLoading.get_pixluts(patient_dict_container.dataset) patient_dict_container.set("pixluts", dict_pixluts) rois = ImageLoading.get_roi_info(ds) patient_dict_container.set("rois", rois) patient_dict_container.set("selected_rois", []) patient_dict_container.set("dict_polygons_axial", {}) patient_dict_container.set("rtss_modified", True)
def __init__(self): # Load test DICOM files if platform.system() == "Windows": desired_path = "\\testdata\\DICOM-RT-TEST" elif platform.system() == "Linux" or platform.system() == "Darwin": desired_path = "/testdata/DICOM-RT-TEST" desired_path = os.path.dirname( os.path.realpath(__file__)) + desired_path selected_files = find_DICOM_files( desired_path) # list of DICOM test files file_path = os.path.dirname( os.path.commonprefix(selected_files)) # file path of DICOM files read_data_dict, file_names_dict = ImageLoading.get_datasets( selected_files) # Create patient dict container object patient_dict_container = PatientDictContainer() patient_dict_container.clear() patient_dict_container.set_initial_values(file_path, read_data_dict, file_names_dict) # Set additional attributes in patient dict container (otherwise program will crash and test will fail) if "rtss" in file_names_dict: dataset_rtss = dcmread(file_names_dict['rtss']) self.rois = ImageLoading.get_roi_info(dataset_rtss) dict_raw_contour_data, dict_numpoints = ImageLoading.get_raw_contour_data( dataset_rtss) dict_pixluts = ImageLoading.get_pixluts(read_data_dict) patient_dict_container.set("rois", self.rois) patient_dict_container.set("raw_contour", dict_raw_contour_data) patient_dict_container.set("num_points", dict_numpoints) patient_dict_container.set("pixluts", dict_pixluts) # Open the main window self.main_window = MainWindow() self.main_window.show() self.dicom_view = self.main_window.dicom_view self.new_polygons = {} slider_id = self.dicom_view.slider.value() self.curr_slice = self.dicom_view.patient_dict_container.get( "dict_uid")[slider_id]
def __init__(self): # Load test DICOM files desired_path = Path.cwd().joinpath('test', 'pet-testdata') # List of DICOM test files selected_files = find_DICOM_files(desired_path) # File path of DICOM files file_path = os.path.dirname(os.path.commonprefix(selected_files)) read_data_dict, file_names_dict = \ ImageLoading.get_datasets(selected_files) # Create patient dict container object self.patient_dict_container = PatientDictContainer() self.patient_dict_container.clear() self.patient_dict_container.set_initial_values \ (file_path, read_data_dict, file_names_dict) # Set additional attributes in patient dict container # (otherwise program will crash and test will fail) self.patient_dict_container.set("existing_rtss_files", []) if "rtss" in file_names_dict: dataset_rtss = dcmread(file_names_dict['rtss']) self.rois = ImageLoading.get_roi_info(dataset_rtss) dict_raw_contour_data, dict_numpoints = \ ImageLoading.get_raw_contour_data(dataset_rtss) dict_pixluts = ImageLoading.get_pixluts(read_data_dict) self.patient_dict_container.set("rois", self.rois) self.patient_dict_container.set("raw_contour", dict_raw_contour_data) self.patient_dict_container.set("num_points", dict_numpoints) self.patient_dict_container.set("pixluts", dict_pixluts) else: img_loader = ImageLoader(selected_files, None, None) img_loader.load_temp_rtss(file_path, DummyProgressWindow, DummyProgressWindow) # Open the main window self.main_window = MainWindow() # Get the initial structure and ROI count self.initial_structure_count = \ self.main_window.structures_tab.layout_content.count() self.initial_roi_count = len(self.main_window.structures_tab.rois)
def structure_modified(self, new_dataset): """ Executes when a structure is renamed/deleted. Displays indicator that structure has changed. """ # TODO there needs to be a way to give the user the option to save the new RTSS file. # Currently all changes are discarded when the user exits the program. # If this is the first time the RTSS has been modified, create a modified indicator giving the user the option # to save their new file. if not self.main_window.rtss_modified: modified_indicator_widget = QtWidgets.QWidget() modified_indicator_widget.setContentsMargins(8, 5, 8, 5) modified_indicator_layout = QtWidgets.QHBoxLayout() modified_indicator_layout.setAlignment(Qt.AlignLeft) modified_indicator_icon = QtWidgets.QLabel() modified_indicator_icon.setPixmap( QtGui.QPixmap("src/Icon/alert.png")) modified_indicator_layout.addWidget(modified_indicator_icon) modified_indicator_text = QtWidgets.QLabel( "Structures have been modified") modified_indicator_text.setStyleSheet("color: red") modified_indicator_layout.addWidget(modified_indicator_text) modified_indicator_widget.setLayout(modified_indicator_layout) modified_indicator_widget.mouseReleaseEvent = self.save_new_rtss # When the widget is clicked, save the rtss self.layout.addWidget(modified_indicator_widget) # If this is the first change made to the RTSS file, update the dataset with the new one so that OnkoDICOM # starts working off this dataset rather than the original RTSS file. self.main_window.rtss_modified = True self.main_window.dataset_rtss = new_dataset # Refresh ROIs in main page self.main_window.rois = ImageLoading.get_roi_info(new_dataset) self.main_window.dict_raw_ContourData, self.main_window.dict_NumPoints = ImageLoading.get_raw_contour_data( new_dataset) self.main_window.list_roi_numbers = self.main_window.ordered_list_rois( ) # Refresh structure tab self.update_content()
def load_temp_rtss(self, path, progress_callback, interrupt_flag): """ Generate a temporary rtss and load its data into MovingDictContainer :param path: str. The common root folder of all DICOM files. :param progress_callback: A signal that receives the current progress of the loading. :param interrupt_flag: A threading.Event() object that tells the function to stop loading. """ progress_callback.emit(("Generating temporary rtss...", 20)) moving_dict_container = MovingDictContainer() rtss_path = Path(path).joinpath('rtss.dcm') uid_list = ImageLoading.get_image_uid_list( moving_dict_container.dataset) rtss = create_initial_rtss_from_ct(moving_dict_container.dataset[0], rtss_path, uid_list) if interrupt_flag.is_set(): # Stop loading. print("stopped") return False progress_callback.emit(("Loading temporary rtss...", 50)) # Set ROIs rois = ImageLoading.get_roi_info(rtss) moving_dict_container.set("rois", rois) # Set pixluts dict_pixluts = ImageLoading.get_pixluts(moving_dict_container.dataset) moving_dict_container.set("pixluts", dict_pixluts) # Add RT Struct file path and dataset to moving dict container moving_dict_container.filepaths['rtss'] = rtss_path moving_dict_container.dataset['rtss'] = rtss # Set some moving dict container attributes moving_dict_container.set("file_rtss", rtss_path) moving_dict_container.set("dataset_rtss", rtss) ordered_dict = DicomTree(None).dataset_to_dict(rtss) moving_dict_container.set("dict_dicom_tree_rtss", ordered_dict) moving_dict_container.set("selected_rois", [])
def __init__(self): # Load test DICOM files desired_path = Path.cwd().joinpath('test', 'testdata') # list of DICOM test files selected_files = find_DICOM_files(desired_path) # file path of DICOM files file_path = os.path.dirname(os.path.commonprefix(selected_files)) read_data_dict, file_names_dict = \ ImageLoading.get_datasets(selected_files) # Create patient dict container object self.patient_dict_container = PatientDictContainer() self.patient_dict_container.clear() self.patient_dict_container.set_initial_values\ (file_path, read_data_dict, file_names_dict) # Set additional attributes in patient dict container # (otherwise program will crash and test will fail) if "rtss" in file_names_dict: dataset_rtss = dcmread(file_names_dict['rtss']) self.rois = ImageLoading.get_roi_info(dataset_rtss) dict_raw_contour_data, dict_numpoints = \ ImageLoading.get_raw_contour_data(dataset_rtss) dict_pixluts = ImageLoading.get_pixluts(read_data_dict) self.patient_dict_container.set("rois", self.rois) self.patient_dict_container.set("raw_contour", dict_raw_contour_data) self.patient_dict_container.set("num_points", dict_numpoints) self.patient_dict_container.set("pixluts", dict_pixluts) # Set location of rtss file file_paths = self.patient_dict_container.filepaths self.patient_dict_container.set("file_rtss", file_paths['rtss']) # Create ISO2ROI object self.iso2roi = ISO2ROI()
def load(self, interrupt_flag, progress_callback): """ :param interrupt_flag: A threading.Event() object that tells the function to stop loading. :param progress_callback: A signal that receives the current progress of the loading. :return: PatientDictContainer object containing all values related to the loaded DICOM files. """ progress_callback.emit(("Creating datasets...", 0)) try: # Gets the common root folder. path = os.path.dirname(os.path.commonprefix(self.selected_files)) read_data_dict, file_names_dict = ImageLoading.get_datasets( self.selected_files) except ImageLoading.NotAllowedClassError: raise ImageLoading.NotAllowedClassError # Populate the initial values in the PatientDictContainer singleton. moving_dict_container = MovingDictContainer() moving_dict_container.clear() moving_dict_container.set_initial_values( path, read_data_dict, file_names_dict, existing_rtss_files=self.existing_rtss) if interrupt_flag.is_set(): print("stopped") return False if 'rtss' in file_names_dict and 'rtdose' in file_names_dict: self.parent_window.signal_advise_calc_dvh.connect( self.update_calc_dvh) self.signal_request_calc_dvh.emit() while not self.advised_calc_dvh: pass if 'rtss' in file_names_dict: dataset_rtss = dcmread(file_names_dict['rtss']) progress_callback.emit(("Getting ROI info...", 10)) rois = ImageLoading.get_roi_info(dataset_rtss) if interrupt_flag.is_set(): # Stop loading. print("stopped") return False progress_callback.emit(("Getting contour data...", 30)) dict_raw_contour_data, dict_numpoints = \ ImageLoading.get_raw_contour_data(dataset_rtss) # Determine which ROIs are one slice thick dict_thickness = ImageLoading.get_thickness_dict( dataset_rtss, read_data_dict) if interrupt_flag.is_set(): # Stop loading. print("stopped") return False progress_callback.emit(("Getting pixel LUTs...", 50)) dict_pixluts = ImageLoading.get_pixluts(read_data_dict) if interrupt_flag.is_set(): # Stop loading. print("stopped") return False # Add RTSS values to MovingDictContainer moving_dict_container.set("rois", rois) moving_dict_container.set("raw_contour", dict_raw_contour_data) moving_dict_container.set("num_points", dict_numpoints) moving_dict_container.set("pixluts", dict_pixluts) if 'rtdose' in file_names_dict and self.calc_dvh: dataset_rtdose = dcmread(file_names_dict['rtdose']) # Spawn-based platforms (i.e Windows and MacOS) have a large # overhead when creating a new process, which ends up making # multiprocessing on these platforms more expensive than linear # calculation. As such, multiprocessing is only available on # Linux until a better solution is found. fork_safe_platforms = ['Linux'] if platform.system() in fork_safe_platforms: progress_callback.emit(("Calculating DVHs...", 60)) raw_dvh = ImageLoading.multi_calc_dvh( dataset_rtss, dataset_rtdose, rois, dict_thickness) else: progress_callback.emit( ("Calculating DVHs... (This may take a while)", 60)) raw_dvh = ImageLoading.calc_dvhs(dataset_rtss, dataset_rtdose, rois, dict_thickness, interrupt_flag) if interrupt_flag.is_set(): # Stop loading. print("stopped") return False progress_callback.emit(("Converging to zero...", 80)) dvh_x_y = ImageLoading.converge_to_0_dvh(raw_dvh) if interrupt_flag.is_set(): # Stop loading. print("stopped") return False # Add DVH values to MovingDictContainer moving_dict_container.set("raw_dvh", raw_dvh) moving_dict_container.set("dvh_x_y", dvh_x_y) moving_dict_container.set("dvh_outdated", False) create_moving_model() else: create_moving_model() self.load_temp_rtss(path, progress_callback, interrupt_flag) progress_callback.emit(("Loading Moving Model", 85)) if interrupt_flag.is_set(): # Stop loading. progress_callback.emit(("Stopping", 85)) return False return True
def load_images(cls, patient_files, required_classes): """ Loads required datasets for the selected patient. :param patient_files: dictionary of classes and patient files. :param required_classes: list of classes required for the selected/current process. :return: True if all required datasets found, false otherwise. """ files = [] found_classes = set() # Loop through each item in patient_files for key, value in patient_files.items(): # If the item is an allowed class if key in cls.allowed_classes: for i in range(len(value)): # Add item's files to the files list files.extend(value[i].get_files()) # Get the modality name modality_name = cls.allowed_classes.get(key).get('name') # If the modality name is not found_classes, add it if modality_name not in found_classes \ and modality_name in required_classes: found_classes.add(modality_name) # Get the difference between required classes and found classes class_diff = set(required_classes).difference(found_classes) # If the dataset is missing required files, pass on it if len(class_diff) > 0: print("Skipping dataset. Missing required file(s) {}".format( class_diff)) return False # Try to get the datasets from the selected files try: # Convert paths to a common file system representation for i, file in enumerate(files): files[i] = Path(file).as_posix() read_data_dict, file_names_dict = cls.get_datasets(files) path = os.path.dirname( os.path.commonprefix(list(file_names_dict.values()))) # Otherwise raise an exception (OnkoDICOM does not support the # selected file type) except ImageLoading.NotAllowedClassError: raise ImageLoading.NotAllowedClassError # Populate the initial values in the PatientDictContainer patient_dict_container = PatientDictContainer() patient_dict_container.clear() patient_dict_container.set_initial_values(path, read_data_dict, file_names_dict) # If an RT Struct is included, set relevant values in the # PatientDictContainer if 'rtss' in file_names_dict: dataset_rtss = dcmread(file_names_dict['rtss']) rois = ImageLoading.get_roi_info(dataset_rtss) dict_raw_contour_data, dict_numpoints = \ ImageLoading.get_raw_contour_data(dataset_rtss) dict_pixluts = ImageLoading.get_pixluts(read_data_dict) # Add RT Struct values to PatientDictContainer patient_dict_container.set("rois", rois) patient_dict_container.set("raw_contour", dict_raw_contour_data) patient_dict_container.set("num_points", dict_numpoints) patient_dict_container.set("pixluts", dict_pixluts) return True
def save_new_rtss_to_fixed_image_set(self, event=None, auto=False): """ Save the current RTSS stored in fixed patient dictionary to the file system. :param event: Not used but will be passed as an argument from modified_indicator_widget on mouseReleaseEvent :param auto: Used for auto save without user confirmation """ existing_rtss_files = self.patient_dict_container.get( "existing_rtss_files") if len(existing_rtss_files) == 1: if isinstance(existing_rtss_files[0], Series): existing_rtss_directory = str( Path(existing_rtss_files[0].get_files()[0])) else: # This "else" is used by iso2roi gui and structure tab tests to # quickly set existing_rtss_directory existing_rtss_directory = existing_rtss_files[0] elif len(existing_rtss_files) > 1: self.display_select_rtss_window() # This function will be called again when a RTSS is selected return else: existing_rtss_directory = None rtss_directory = str(Path( self.patient_dict_container.get("file_rtss"))) if auto: confirm_save = QtWidgets.QMessageBox.Yes else: confirm_save = \ QtWidgets.QMessageBox.information(self, "Confirmation", "Are you sure you want to " "save the modified RTSTRUCT " "file? This will overwrite " "the existing file. This is " "not reversible.", QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No) if confirm_save == QtWidgets.QMessageBox.Yes: if existing_rtss_directory is None: self.patient_dict_container.get("dataset_rtss").save_as( rtss_directory) else: new_rtss = self.patient_dict_container.get("dataset_rtss") old_rtss = pydicom.dcmread(existing_rtss_directory, force=True) old_roi_names = \ set(value["name"] for value in ImageLoading.get_roi_info(old_rtss).values()) new_roi_names = \ set(value["name"] for value in self.patient_dict_container.get("rois").values()) duplicated_names = old_roi_names.intersection(new_roi_names) # stop if there are conflicting roi names and user do not # wish to proceed. if duplicated_names and not self.display_confirm_merge( duplicated_names): return merged_rtss = merge_rtss(old_rtss, new_rtss, duplicated_names) merged_rtss.save_as(existing_rtss_directory) if not auto: QtWidgets.QMessageBox.about( self.parentWidget(), "File saved", "The RTSTRUCT file has been saved.") self.patient_dict_container.set("rtss_modified", False) # Hide the modified indicator self.modified_indicator_widget.setVisible(False)
def fixed_container_structure_modified(self, changes): """ Executes when a structure of fixed patient container is modified Displays indicator that structure has changed. Changes is a tuple of (new_dataset, description_of_changes) description_of_changes follows the format {"type_of_change": value_of_change}. Examples: {"rename": ["TOOTH", "TEETH"]} represents that the TOOTH structure has been renamed to TEETH. {"delete": ["TEETH", "MAXILLA"]} represents that the TEETH and MAXILLA structures have been deleted. {"draw": "AORTA"} represents that a new structure AORTA has been drawn. Note: Use {"draw": None} after multiple ROIs are generated (E.g., from ISO2ROI functionality), and use {"transfer":None} for ROI Transfer instead of calling this function multiple times. This will trigger auto save. """ new_dataset = changes[0] change_description = changes[1] # Only show the modified indicator if description_of_changes is # not {"draw": None}, as this description means that the RTSS # is autosaved, and therefore there is no need to tell the user # that the RTSS has been modified if not("draw" in change_description and change_description["draw"] is None) and \ not ("transfer" in change_description): self.show_modified_indicator() # If this is the first change made to the RTSS file, update the # dataset with the new one so that OnkoDICOM starts working off this # dataset rather than the original RTSS file. self.patient_dict_container.set("rtss_modified", True) self.patient_dict_container.set("dataset_rtss", new_dataset) # Refresh ROIs in main page self.patient_dict_container.set("rois", ImageLoading.get_roi_info(new_dataset)) self.rois = self.patient_dict_container.get("rois") contour_data = ImageLoading.get_raw_contour_data(new_dataset) self.patient_dict_container.set("raw_contour", contour_data[0]) self.patient_dict_container.set("num_points", contour_data[1]) pixluts = ImageLoading.get_pixluts(self.patient_dict_container.dataset) self.patient_dict_container.set("pixluts", pixluts) self.patient_dict_container.set( "list_roi_numbers", ordered_list_rois(self.patient_dict_container.get("rois"))) self.patient_dict_container.set("selected_rois", []) self.patient_dict_container.set("dict_polygons_axial", {}) self.patient_dict_container.set("dict_polygons_sagittal", {}) self.patient_dict_container.set("dict_polygons_coronal", {}) if "draw" in change_description or "transfer" in change_description: dicom_tree_rtss = DicomTree(None) dicom_tree_rtss.dataset = new_dataset dicom_tree_rtss.dict = dicom_tree_rtss.dataset_to_dict( dicom_tree_rtss.dataset) self.patient_dict_container.set("dict_dicom_tree_rtss", dicom_tree_rtss.dict) self.color_dict = self.init_color_roi(self.patient_dict_container) self.patient_dict_container.set("roi_color_dict", self.color_dict) if self.patient_dict_container.has_attribute("raw_dvh"): # DVH will be outdated once changes to it are made, and # recalculation will be required. self.patient_dict_container.set("dvh_outdated", True) if self.patient_dict_container.has_attribute("raw_dvh"): # Rename structures in DVH list if "rename" in change_description: new_raw_dvh = self.patient_dict_container.get("raw_dvh") for key, dvh in new_raw_dvh.items(): if dvh.name == change_description["rename"][0]: dvh.name = change_description["rename"][1] break self.patient_dict_container.set("raw_dvh", new_raw_dvh) dvh2rtdose(new_raw_dvh) # Remove structures from DVH list - the only visible effect of # this section is the exported DVH csv if "delete" in change_description: list_of_deleted = [] new_raw_dvh = self.patient_dict_container.get("raw_dvh") for key, dvh in new_raw_dvh.items(): if dvh.name in change_description["delete"]: list_of_deleted.append(key) for key in list_of_deleted: new_raw_dvh.pop(key) self.patient_dict_container.set("raw_dvh", new_raw_dvh) dvh2rtdose(new_raw_dvh) # Refresh ROIs in DVH tab and DICOM View self.request_update_structures.emit() # Refresh structure tab self.update_content() if "draw" in change_description and change_description["draw"] is None: self.save_new_rtss_to_fixed_image_set(auto=True) elif "transfer" in change_description \ and change_description["transfer"] is None: self.save_new_rtss_to_fixed_image_set(auto=True)
def test_merge_rtss(qtbot, test_object): """Test merging rtss. This function creates a new rtss, then merges the new rtss with the old rtss and asserts that duplicated ROIs will be overwritten when the other being merged. :param test_object: test_object function, for accessing the shared TestStructureTab object. """ patient_dict_container = PatientDictContainer() # Create a new rtss dataset = patient_dict_container.dataset[0] rtss_path = Path(patient_dict_container.path).joinpath('rtss.dcm') new_rtss = create_initial_rtss_from_ct( dataset, rtss_path, ImageLoading.get_image_uid_list(patient_dict_container.dataset)) # Set ROIs rois = ImageLoading.get_roi_info(new_rtss) patient_dict_container.set("rois", rois) # Add a new ROI into the new rtss with the name of the first ROI in # the old rtss roi_name = test_object.rois.get(1)["name"] roi_coordinates = [0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0] new_rtss = create_roi(new_rtss, roi_name, [{ 'coords': roi_coordinates, 'ds': dataset }]) # Add a new ROI with a new name roi_name = "NewTestROI" new_rtss = create_roi(new_rtss, roi_name, [{ 'coords': roi_coordinates, 'ds': dataset }]) # Set ROIs rois = ImageLoading.get_roi_info(new_rtss) patient_dict_container.set("rois", rois) patient_dict_container.set("existing_file_rtss", patient_dict_container.get("file_rtss")) patient_dict_container.set("dataset_rtss", new_rtss) # Merge the old and new rtss structure_tab = StructureTab() structure_tab.show_modified_indicator() qtbot.addWidget(structure_tab) def test_message_window(): messagebox = structure_tab.findChild(QtWidgets.QMessageBox) assert messagebox is not None yes_button = messagebox.buttons()[1] qtbot.mouseClick(yes_button, QtCore.Qt.LeftButton, delay=1) QtCore.QTimer.singleShot(1000, test_message_window) structure_tab.save_new_rtss_to_fixed_image_set(auto=True) merged_rtss = pydicom.read_file(patient_dict_container.get("file_rtss")) merged_rois = ImageLoading.get_roi_info(merged_rtss) assert (len(test_object.rois) + 1 == len(merged_rois))
def structure_modified(self, changes): """ Executes when a structure is renamed/deleted. Displays indicator that structure has changed. changes is a tuple of (new_dataset, description_of_changes) description_of_changes follows the format {"type_of_change": value_of_change}. Examples: {"rename": ["TOOTH", "TEETH"]} represents that the TOOTH structure has been renamed to TEETH. {"delete": ["TEETH", "MAXILLA"]} represents that the TEETH and MAXILLA structures have been deleted. {"draw": "AORTA"} represents that a new structure AORTA has been drawn. """ new_dataset = changes[0] change_description = changes[1] # If this is the first time the RTSS has been modified, create a modified indicator giving the user the option # to save their new file. if self.patient_dict_container.get("rtss_modified") is False: self.show_modified_indicator() # If this is the first change made to the RTSS file, update the dataset with the new one so that OnkoDICOM # starts working off this dataset rather than the original RTSS file. self.patient_dict_container.set("rtss_modified", True) self.patient_dict_container.set("dataset_rtss", new_dataset) # Refresh ROIs in main page self.patient_dict_container.set("rois", ImageLoading.get_roi_info(new_dataset)) self.rois = self.patient_dict_container.get("rois") contour_data = ImageLoading.get_raw_contour_data(new_dataset) self.patient_dict_container.set("raw_contour", contour_data[0]) self.patient_dict_container.set("num_points", contour_data[1]) pixluts = ImageLoading.get_pixluts(self.patient_dict_container.dataset) self.patient_dict_container.set("pixluts", pixluts) self.patient_dict_container.set( "list_roi_numbers", ordered_list_rois(self.patient_dict_container.get("rois"))) self.patient_dict_container.set("selected_rois", []) self.patient_dict_container.set("dict_polygons", {}) if "draw" in change_description: dicom_tree_rtss = DicomTree(None) dicom_tree_rtss.dataset = new_dataset dicom_tree_rtss.dict = dicom_tree_rtss.dataset_to_dict( dicom_tree_rtss.dataset) self.patient_dict_container.set("dict_dicom_tree_rtss", dicom_tree_rtss.dict) self.color_dict = self.init_color_roi() self.patient_dict_container.set("roi_color_dict", self.color_dict) if self.patient_dict_container.has_attribute("raw_dvh"): # DVH will be outdated once changes to it are made, and recalculation will be required. self.patient_dict_container.set("dvh_outdated", True) if self.patient_dict_container.has_modality("raw_dvh"): # Rename structures in DVH list if "rename" in changes[1]: new_raw_dvh = self.patient_dict_container.get("raw_dvh") for key, dvh in new_raw_dvh.items(): if dvh.name == change_description["rename"][0]: dvh.name = change_description["rename"][1] break self.patient_dict_container.set("raw_dvh", new_raw_dvh) # Remove structures from DVH list - the only visible effect of this section is the exported DVH csv if "delete" in changes[1]: list_of_deleted = [] new_raw_dvh = self.patient_dict_container.get("raw_dvh") for key, dvh in new_raw_dvh.items(): if dvh.name in change_description["delete"]: list_of_deleted.append(key) for key in list_of_deleted: new_raw_dvh.pop(key) self.patient_dict_container.set("raw_dvh", new_raw_dvh) # Refresh ROIs in DVH tab and DICOM View self.request_update_structures.emit() # Refresh structure tab self.update_content()
def generate_ROI(self, contours, progress_callback): """ Generates new ROIs based on contour data. :param contours: dictionary of contours to turn into ROIs. :param progress_callback: signal that receives the current progress of the loading. """ # Initialise variables needed for function patient_dict_container = PatientDictContainer() dataset_rtss = patient_dict_container.get("dataset_rtss") # Get existing ROIs existing_rois = [] rois = patient_dict_container.get("dataset_rtss") if rois: for roi in rois.StructureSetROISequence: existing_rois.append(roi.ROIName) # Loop through each SUV level item_count = len(contours) current_progress = 60 progress_increment = round((95 - 60) / item_count) for item in contours: # Delete ROI if it already exists to recreate it if item in existing_rois: dataset_rtss = ROI.delete_roi(dataset_rtss, item) # Update patient dict container current_rois = patient_dict_container.get("rois") keys = [] for key, value in current_rois.items(): if value["name"] == item: keys.append(key) for key in keys: del current_rois[key] patient_dict_container.set("rois", current_rois) progress_callback.emit(("Generating ROIs", current_progress)) current_progress += progress_increment # Loop through each slice for i in range(len(contours[item])): slider_id = contours[item][i][0] dataset = patient_dict_container.dataset[slider_id] pixlut = patient_dict_container.get("pixluts") pixlut = pixlut[dataset.SOPInstanceUID] z_coord = dataset.SliceLocation # List storing lists that contain all points for a # contour. single_array = [] # Loop through each contour for j in range(len(contours[item][i][1])): single_array.append([]) # Loop through every point in the contour for point in contours[item][i][1][j]: # Convert pixel coordinates to RCS points rcs_pixels = ROI.pixel_to_rcs(pixlut, round(point[1]), round(point[0])) # Append RCS points to the single array single_array[j].append(rcs_pixels[0]) single_array[j].append(rcs_pixels[1]) single_array[j].append(z_coord) # Create the ROI(s) for array in single_array: rtss = ROI.create_roi(dataset_rtss, item, [{ 'coords': array, 'ds': dataset }], "") # Save the updated rtss patient_dict_container.set("dataset_rtss", rtss) patient_dict_container.set("rois", ImageLoading.get_roi_info(rtss))
def structure_modified(self, changes): """ Executes when a structure is renamed/deleted. Displays indicator that structure has changed. changes is a tuple of (new_dataset, description_of_changes) description_of_changes follows the format {"type_of_change": value_of_change}. Examples: {"rename": ["TOOTH", "TEETH"]} represents that the TOOTH structure has been renamed to TEETH. {"delete": ["TEETH", "MAXILLA"]} represents that the TEETH and MAXILLA structures have been deleted. """ new_dataset = changes[0] change_description = changes[1] # If this is the first time the RTSS has been modified, create a modified indicator giving the user the option # to save their new file. if not self.main_window.rtss_modified: modified_indicator_widget = QtWidgets.QWidget() modified_indicator_widget.setContentsMargins(8, 5, 8, 5) modified_indicator_layout = QtWidgets.QHBoxLayout() modified_indicator_layout.setAlignment(Qt.AlignLeft) modified_indicator_icon = QtWidgets.QLabel() modified_indicator_icon.setPixmap( QtGui.QPixmap("src/Icon/alert.png")) modified_indicator_layout.addWidget(modified_indicator_icon) modified_indicator_text = QtWidgets.QLabel( "Structures have been modified") modified_indicator_text.setStyleSheet("color: red") modified_indicator_layout.addWidget(modified_indicator_text) modified_indicator_widget.setLayout(modified_indicator_layout) modified_indicator_widget.mouseReleaseEvent = self.save_new_rtss # When the widget is clicked, save the rtss self.layout.addWidget(modified_indicator_widget) # If this is the first change made to the RTSS file, update the dataset with the new one so that OnkoDICOM # starts working off this dataset rather than the original RTSS file. self.main_window.rtss_modified = True self.main_window.dataset_rtss = new_dataset # Refresh ROIs in main page self.main_window.rois = ImageLoading.get_roi_info(new_dataset) self.main_window.dict_raw_ContourData, self.main_window.dict_NumPoints = ImageLoading.get_raw_contour_data( new_dataset) self.main_window.list_roi_numbers = self.main_window.ordered_list_rois( ) self.main_window.selected_rois = [] if self.main_window.raw_dvh is not None: # Rename structures in DVH list if "rename" in changes[1]: for key, dvh in self.main_window.raw_dvh.items(): if dvh.name == change_description["rename"][0]: dvh.name = change_description["rename"][1] break # Remove structures from DVH list - the only visible effect of this section is the exported DVH csv if "delete" in changes[1]: list_of_deleted = [] for key, dvh in self.main_window.raw_dvh.items(): if dvh.name in change_description["delete"]: list_of_deleted.append(key) for key in list_of_deleted: self.main_window.raw_dvh.pop(key) # Refresh ROIs in DVH tab and DICOM View if hasattr(self.main_window, 'dvh'): self.main_window.dvh.update_plot(self.main_window) self.main_window.dicom_view.update_view() # Refresh structure tab self.update_content()
def generate_roi(self, contours, progress_callback): """ Generates new ROIs based on contour data. :param contours: dictionary of contours to turn into ROIs. :param progress_callback: signal to update loading progress """ # Initialise variables needed for function patient_dict_container = PatientDictContainer() dataset_rtss = patient_dict_container.get("dataset_rtss") pixmaps = patient_dict_container.get("pixmaps_axial") slider_min = 0 slider_max = len(pixmaps) - 1 # Get existing ROIs existing_rois = [] rois = patient_dict_container.get("dataset_rtss") if rois: for roi in rois.StructureSetROISequence: existing_rois.append(roi.ROIName) # Loop through each isodose level for item in contours: # Delete ROI if it already exists to recreate it if item in existing_rois: dataset_rtss = ROI.delete_roi(dataset_rtss, item) # Update patient dict container current_rois = patient_dict_container.get("rois") keys = [] for key, value in current_rois.items(): if value["name"] == item: keys.append(key) for key in keys: del current_rois[key] patient_dict_container.set("rois", current_rois) # Calculate isodose ROI for each slice, skip if slice has no # contour data for i in range(slider_min, slider_max): if not len(contours[item][i]): continue # Get required data for calculating ROI dataset = patient_dict_container.dataset[i] pixlut = patient_dict_container.get("pixluts") pixlut = pixlut[dataset.SOPInstanceUID] z_coord = dataset.SliceLocation curr_slice_uid = patient_dict_container.get("dict_uid")[i] dose_pixluts = patient_dict_container.get("dose_pixluts") dose_pixluts = dose_pixluts[curr_slice_uid] # Loop through each contour for each slice. # Convert the pixel points to RCS points, append z value single_array = [] # Loop through each contour for j in range(len(contours[item][i])): single_array.append([]) # Loop through every second point in the contour for point in contours[item][i][j][::2]: # Transform into dose pixel dose_pixels = [ dose_pixluts[0][int(point[1])], dose_pixluts[1][int(point[0])] ] # Transform into RCS pixel rcs_pixels = ROI.pixel_to_rcs(pixlut, round(dose_pixels[0]), round(dose_pixels[1])) # Append point coordinates to the single array single_array[j].append(rcs_pixels[0]) single_array[j].append(rcs_pixels[1]) single_array[j].append(z_coord) # Create the ROI(s) for array in single_array: rtss = ROI.create_roi(dataset_rtss, item, [{ 'coords': array, 'ds': dataset }], "DOSE_REGION") # Save the updated rtss patient_dict_container.set("dataset_rtss", rtss) patient_dict_container.set("rois", ImageLoading.get_roi_info(rtss)) progress_callback.emit(("Writing to RT Structure Set", 85))
def load(self, interrupt_flag, progress_callback): """ :param interrupt_flag: A threading.Event() object that tells the function to stop loading. :param progress_callback: A signal that receives the current progress of the loading. :return: PatientDictContainer object containing all values related to the loaded DICOM files. """ progress_callback.emit(("Creating datasets...", 0)) try: path = os.path.dirname(os.path.commonprefix(self.selected_files)) # Gets the common root folder. read_data_dict, file_names_dict = ImageLoading.get_datasets(self.selected_files) except ImageLoading.NotAllowedClassError: raise ImageLoading.NotAllowedClassError # Populate the initial values in the PatientDictContainer singleton. patient_dict_container = PatientDictContainer() patient_dict_container.clear() patient_dict_container.set_initial_values(path, read_data_dict, file_names_dict) # As there is no way to interrupt a QRunnable, this method must check after every step whether or not the # interrupt flag has been set, in which case it will interrupt this method after the currently processing # function has finished running. It's not very pretty, and the thread will still run some functions for, in some # cases, up to a couple seconds after the close button on the Progress Window has been clicked, however it's # the best solution I could come up with. If you have a cleaner alternative, please make your contribution. if interrupt_flag.is_set(): print("stopped") return False if 'rtss' in file_names_dict and 'rtdose' in file_names_dict: self.parent_window.signal_advise_calc_dvh.connect(self.update_calc_dvh) self.signal_request_calc_dvh.emit() while not self.advised_calc_dvh: pass if 'rtss' in file_names_dict: dataset_rtss = dcmread(file_names_dict['rtss']) progress_callback.emit(("Getting ROI info...", 10)) rois = ImageLoading.get_roi_info(dataset_rtss) if interrupt_flag.is_set(): # Stop loading. print("stopped") return False progress_callback.emit(("Getting contour data...", 30)) dict_raw_contour_data, dict_numpoints = ImageLoading.get_raw_contour_data(dataset_rtss) # Determine which ROIs are one slice thick dict_thickness = ImageLoading.get_thickness_dict(dataset_rtss, read_data_dict) if interrupt_flag.is_set(): # Stop loading. print("stopped") return False progress_callback.emit(("Getting pixel LUTs...", 50)) dict_pixluts = ImageLoading.get_pixluts(read_data_dict) if interrupt_flag.is_set(): # Stop loading. print("stopped") return False # Add RTSS values to PatientDictContainer patient_dict_container.set("rois", rois) patient_dict_container.set("raw_contour", dict_raw_contour_data) patient_dict_container.set("num_points", dict_numpoints) patient_dict_container.set("pixluts", dict_pixluts) if 'rtdose' in file_names_dict and self.calc_dvh: dataset_rtdose = dcmread(file_names_dict['rtdose']) # Spawn-based platforms (i.e Windows and MacOS) have a large overhead when creating a new process, which # ends up making multiprocessing on these platforms more expensive than linear calculation. As such, # multiprocessing is only available on Linux until a better solution is found. fork_safe_platforms = ['Linux'] if platform.system() in fork_safe_platforms: progress_callback.emit(("Calculating DVHs...", 60)) raw_dvh = ImageLoading.multi_calc_dvh(dataset_rtss, dataset_rtdose, rois, dict_thickness) else: progress_callback.emit(("Calculating DVHs... (This may take a while)", 60)) raw_dvh = ImageLoading.calc_dvhs(dataset_rtss, dataset_rtdose, rois, dict_thickness, interrupt_flag) if interrupt_flag.is_set(): # Stop loading. print("stopped") return False progress_callback.emit(("Converging to zero...", 80)) dvh_x_y = ImageLoading.converge_to_0_dvh(raw_dvh) if interrupt_flag.is_set(): # Stop loading. print("stopped") return False # Add DVH values to PatientDictContainer patient_dict_container.set("raw_dvh", raw_dvh) patient_dict_container.set("dvh_x_y", dvh_x_y) patient_dict_container.set("dvh_outdated", False) return True else: return True return True