Exemple #1
0
class UIMainWindow:
    """
    The central class responsible for initializing most of the values stored
    in the PatientDictContainer model and defining the visual layout of the
    main window of OnkoDICOM. No class has access to the attributes
    belonging to this class, except for the class's ActionHandler, which is
    used to trigger actions within the main window. Components of this class
    (i.e. QWidget child classes such as StructureTab, DicomView, DicomTree,
    etc.) should not be able to reference this class, and rather should
    exist independently and only be able to communicate with the
    PatientDictContainer model. If a component needs to communicate with
    another component, that should be accomplished by emitting signals
    within that components, and having the slots for those signals within
    this class (as demonstrated by the update_views() method of this class).
    If a class needs to trigger one of the actions defined in the
    ActionHandler, then the instance of the ActionHandler itself can safely
    be passed into the class.
    """
    pyradi_trigger = QtCore.Signal(str, dict, str)

    # Connect to GUIController
    image_fusion_main_window = QtCore.Signal()

    def setup_ui(self, main_window_instance):
        self.main_window_instance = main_window_instance
        self.call_class = MainPageCallClass()
        self.add_on_options_controller = AddOptions(self)

        ##########################################
        #  IMPLEMENTATION OF THE MAIN PAGE VIEW  #
        ##########################################
        if platform.system() == 'Darwin':
            self.stylesheet_path = "res/stylesheet.qss"
        else:
            self.stylesheet_path = "res/stylesheet-win-linux.qss"
        self.stylesheet = open(resource_path(self.stylesheet_path)).read()
        window_icon = QIcon()
        window_icon.addPixmap(QPixmap(resource_path(
            "res/images/icon.ico")), QIcon.Normal, QIcon.Off)
        self.main_window_instance.setMinimumSize(1080, 700)
        self.main_window_instance.setObjectName("MainOnkoDicomWindowInstance")
        self.main_window_instance.setWindowIcon(window_icon)
        self.main_window_instance.setStyleSheet(self.stylesheet)

        self.setup_central_widget()
        self.setup_actions()

        # Create SUV2ROI object and connect signals
        self.suv2roi = SUV2ROI()
        self.suv2roi_progress_window = \
            ProgressWindow(self.main_window_instance,
                           QtCore.Qt.WindowTitleHint |
                           QtCore.Qt.WindowCloseButtonHint)
        self.suv2roi_progress_window.signal_loaded.connect(
            self.on_loaded_suv2roi)

    def setup_actions(self):
        if hasattr(self, 'toolbar'):
            self.main_window_instance.removeToolBar(self.toolbar)
        self.action_handler = ActionHandler(self)
        self.menubar = MenuBar(self.action_handler)
        self.main_window_instance.setMenuBar(self.menubar)
        self.toolbar = Toolbar(self.action_handler)
        self.main_window_instance.addToolBar(
            QtCore.Qt.TopToolBarArea, self.toolbar)
        self.main_window_instance.setWindowTitle("OnkoDICOM")

    def setup_central_widget(self):
        patient_dict_container = PatientDictContainer()
        self.central_widget = QtWidgets.QWidget()
        self.central_widget_layout = QVBoxLayout()

        self.patient_bar = PatientBar()

        splitter = QtWidgets.QSplitter(QtCore.Qt.Horizontal)

        # Left panel contains stuctures tab, isodoses tab,
        # and structure information
        self.left_panel = QtWidgets.QTabWidget()
        self.left_panel.setMinimumWidth(300)
        self.left_panel.setMaximumWidth(500)

        # Add structures tab to left panel
        if not hasattr(self, 'structures_tab'):
            self.structures_tab = StructureTab()
            self.structures_tab.request_update_structures.connect(
                self.update_views)
        else:
            self.structures_tab.update_ui()
        self.left_panel.addTab(self.structures_tab, "Structures")

        if patient_dict_container.has_modality("rtdose"):
            self.isodoses_tab = IsodoseTab()
            self.isodoses_tab.request_update_isodoses.connect(
                self.update_views)
            self.isodoses_tab.request_update_ui.connect(
                self.structures_tab.fixed_container_structure_modified)
            self.left_panel.addTab(self.isodoses_tab, "Isodoses")
        elif hasattr(self, 'isodoses_tab'):
            del self.isodoses_tab

        # Right panel contains the different tabs of DICOM view, DVH,
        # clinical data, DICOM tree
        self.right_panel = QtWidgets.QTabWidget()

        # Create a Dicom View containing single-slice and 3-slice views
        self.dicom_view = DicomStackedWidget(self.format_data)

        roi_color_dict = self.structures_tab.color_dict if hasattr(
            self, 'structures_tab') else None
        iso_color_dict = self.isodoses_tab.color_dict if hasattr(
            self, 'isodoses_tab') else None
        self.dicom_single_view = DicomAxialView(
            roi_color=roi_color_dict, iso_color=iso_color_dict)
        self.dicom_axial_view = DicomAxialView(
            is_four_view=True, roi_color=roi_color_dict, iso_color=iso_color_dict,
            metadata_formatted=True, cut_line_color=QtGui.QColor(255, 0, 0))
        self.dicom_sagittal_view = DicomSagittalView(
            roi_color=roi_color_dict, iso_color=iso_color_dict,
            cut_line_color=QtGui.QColor(0, 255, 0))
        self.dicom_coronal_view = DicomCoronalView(
            roi_color=roi_color_dict, iso_color=iso_color_dict,
            cut_line_color=QtGui.QColor(0, 0, 255))
        self.three_dimension_view = DicomView3D()

        # Rescale the size of the scenes inside the 3-slice views
        self.dicom_axial_view.zoom = INITIAL_FOUR_VIEW_ZOOM
        self.dicom_sagittal_view.zoom = INITIAL_FOUR_VIEW_ZOOM
        self.dicom_coronal_view.zoom = INITIAL_FOUR_VIEW_ZOOM
        self.dicom_axial_view.update_view(zoom_change=True)
        self.dicom_sagittal_view.update_view(zoom_change=True)
        self.dicom_coronal_view.update_view(zoom_change=True)

        self.dicom_four_views = QWidget()
        self.dicom_four_views_layout = QGridLayout()
        for i in range(2):
            self.dicom_four_views_layout.setColumnStretch(i, 1)
            self.dicom_four_views_layout.setRowStretch(i, 1)
        self.dicom_four_views_layout.addWidget(self.dicom_axial_view, 0, 0)
        self.dicom_four_views_layout.addWidget(self.dicom_sagittal_view, 0, 1)
        self.dicom_four_views_layout.addWidget(self.dicom_coronal_view, 1, 0)
        self.dicom_four_views_layout.addWidget(self.three_dimension_view, 1, 1)
        self.dicom_four_views.setLayout(self.dicom_four_views_layout)

        self.dicom_view.addWidget(self.dicom_four_views)
        self.dicom_view.addWidget(self.dicom_single_view)
        self.dicom_view.setCurrentWidget(self.dicom_single_view)

        # Add DICOM View to right panel as a tab
        self.right_panel.addTab(self.dicom_view, "DICOM View")

        # Add PETVT View to right panel as a tab
        self.pet_ct_tab = PetCtView()
        self.right_panel.addTab(self.pet_ct_tab, "PET/CT View")

        # Add DVH tab to right panel as a tab
        if patient_dict_container.has_modality("rtdose"):
            self.dvh_tab = DVHTab()
            self.right_panel.addTab(self.dvh_tab, "DVH")
        elif hasattr(self, 'dvh_tab'):
            del self.dvh_tab

        # Add DICOM Tree View tab
        self.dicom_tree = DicomTreeView()
        self.right_panel.addTab(self.dicom_tree, "DICOM Tree")

        # Connect SUV2ROI signal to handler function
        self.dicom_single_view.suv2roi_signal.connect(self.perform_suv2roi)

        # Add clinical data tab
        self.call_class.display_clinical_data(self.right_panel)

        splitter.addWidget(self.left_panel)
        splitter.addWidget(self.right_panel)

        # Create footer
        self.footer = QtWidgets.QWidget()
        self.create_footer()

        # Set layout
        self.central_widget_layout.addWidget(self.patient_bar)
        self.central_widget_layout.addWidget(splitter)
        self.central_widget_layout.addWidget(self.footer)

        self.central_widget.setLayout(self.central_widget_layout)
        self.main_window_instance.setCentralWidget(self.central_widget)

    def create_footer(self):
        self.footer.setFixedHeight(15)
        layout_footer = QtWidgets.QHBoxLayout(self.footer)
        layout_footer.setContentsMargins(0, 0, 0, 0)

        label_footer = QtWidgets.QLabel("@OnkoDICOM2021")
        label_footer.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignRight)

        layout_footer.addWidget(label_footer)

    def update_views(self, update_3d_window=False):
        """
        This function is a slot for signals to request the updating of the
        DICOM View and DVH tabs in order to reflect changes made by other
        components of the main window (for example, when a structure in the
        structures tab is selected, this method needs to be called in order
        for the DICOM view window to be updated to show the new region of
        interest.

        :param update_3d_window: a boolean to mark if 3d model
        needs to be updated
        """

        self.dicom_single_view.update_view()
        self.dicom_axial_view.update_view()
        self.dicom_coronal_view.update_view()
        self.dicom_sagittal_view.update_view()

        if update_3d_window:
            self.three_dimension_view.update_view()

        if hasattr(self, 'dvh_tab'):
            self.dvh_tab.update_plot()

        if hasattr(self, 'pet_ct_tab'):
            if self.pet_ct_tab.initialised:
                self.pet_ct_tab.update_view()

        if hasattr(self, 'image_fusion_view'):
            if self.image_fusion_view_axial is not None:
                self.image_fusion_single_view.update_view()
                self.image_fusion_view_axial.update_view()
                self.image_fusion_view_coronal.update_view()
                self.image_fusion_view_sagittal.update_view()

    def toggle_cut_lines(self):
        if self.dicom_axial_view.horizontal_view is None or \
                self.dicom_axial_view.vertical_view is None or \
                self.dicom_coronal_view.horizontal_view is None or \
                self.dicom_coronal_view.vertical_view is None or \
                self.dicom_sagittal_view.horizontal_view is None or \
                self.dicom_sagittal_view.vertical_view is None:
            self.dicom_axial_view.set_views(self.dicom_coronal_view,
                                            self.dicom_sagittal_view)
            self.dicom_coronal_view.set_views(self.dicom_axial_view,
                                              self.dicom_sagittal_view)
            self.dicom_sagittal_view.set_views(self.dicom_axial_view,
                                               self.dicom_coronal_view)
        else:
            self.dicom_axial_view.set_views(None, None)
            self.dicom_coronal_view.set_views(None, None)
            self.dicom_sagittal_view.set_views(None, None)

        if hasattr(self, 'image_fusion_view'):
            if self.image_fusion_view is not None:
                if self.image_fusion_view_axial.horizontal_view is None or \
                        self.image_fusion_view_axial.vertical_view is None or \
                        self.image_fusion_view_coronal.horizontal_view is None \
                        or self.image_fusion_view_coronal.vertical_view is None \
                        or \
                        self.image_fusion_view_sagittal.horizontal_view is None \
                        or \
                        self.image_fusion_view_sagittal.vertical_view is None:
                    self.image_fusion_view_axial.set_views(
                        self.image_fusion_view_coronal,
                        self.image_fusion_view_sagittal)
                    self.image_fusion_view_coronal.set_views(
                        self.image_fusion_view_axial,
                        self.image_fusion_view_sagittal)

                    self.image_fusion_view_sagittal.set_views(
                        self.image_fusion_view_axial,
                        self.image_fusion_view_coronal)
                else:
                    self.image_fusion_view_axial.set_views(None, None)
                    self.image_fusion_view_coronal.set_views(None, None)
                    self.image_fusion_view_sagittal.set_views(None, None)

    def zoom_in(self, is_four_view, image_reg_single, image_reg_four):
        """
        This function calls the zooming in function on the four view's views
        or the single view depending on what view is showing on screen.
        is_four_view: Whether the four view is showing
        """
        if is_four_view:
            self.dicom_axial_view.zoom_in()
            self.dicom_coronal_view.zoom_in()
            self.dicom_sagittal_view.zoom_in()
        else:
            self.dicom_single_view.zoom_in()

        if image_reg_single:
            self.image_fusion_single_view.zoom_in()

        if image_reg_four:
            self.image_fusion_view_axial.zoom_in()
            self.image_fusion_view_coronal.zoom_in()
            self.image_fusion_view_sagittal.zoom_in()

        if self.pet_ct_tab.initialised:
            self.pet_ct_tab.zoom_in()

    def zoom_out(self, is_four_view, image_reg_single, image_reg_four):
        """
        This function calls the zooming out function on the four view's
        views or the single view depending on what view is showing on screen.
        is_four_view: Whether the four view is showing
        """
        if is_four_view:
            self.dicom_axial_view.zoom_out()
            self.dicom_coronal_view.zoom_out()
            self.dicom_sagittal_view.zoom_out()
        else:
            self.dicom_single_view.zoom_out()

        if image_reg_single:
            self.image_fusion_single_view.zoom_out()

        if image_reg_four:
            self.image_fusion_view_axial.zoom_out()
            self.image_fusion_view_coronal.zoom_out()
            self.image_fusion_view_sagittal.zoom_out()

        if self.pet_ct_tab.initialised:
            self.pet_ct_tab.zoom_out()

    def format_data(self, size):
        """
        This function is used to update the meta data's font size and margin
        based on the height and width of the viewports.
        size: The size of the DicomStackedWidget
        """
        self.dicom_axial_view.format_metadata(size)

    def create_image_fusion_tab(self):
        """
        This function is used to create the tab for image fusion.
        Function checks if the moving dict container contains rtss to
        load rtss. Views are created and stacked into three window view.
        """
        # Set a flag for Zooming
        self.action_handler.has_image_registration_four = True

        # Instance of Moving Model
        moving_dict_container = MovingDictContainer()

        if moving_dict_container.has_modality("rtss"):
            if len(self.structures_tab.rois.items()) == 0:
                self.structures_tab.update_ui(moving=True)
            # else:
            # TODO: Display both ROIs in the same tab

        self.image_fusion_single_view \
            = ImageFusionAxialView()

        self.image_fusion_view = QStackedWidget()
        self.image_fusion_view_axial = ImageFusionAxialView(
            metadata_formatted=False,
            cut_line_color=QtGui.QColor(255, 0, 0))
        self.image_fusion_view_sagittal = ImageFusionSagittalView(
            cut_line_color=QtGui.QColor(0, 255, 0))
        self.image_fusion_view_coronal = ImageFusionCoronalView(
            cut_line_color=QtGui.QColor(0, 0, 255))
        self.image_fusion_roi_transfer_option_view = ROITransferOptionView(
            self.structures_tab.fixed_container_structure_modified,
            self.structures_tab.moving_container_structure_modified)

        # Rescale the size of the scenes inside the 3-slice views
        self.image_fusion_view_axial.zoom = INITIAL_FOUR_VIEW_ZOOM
        self.image_fusion_view_sagittal.zoom = INITIAL_FOUR_VIEW_ZOOM
        self.image_fusion_view_coronal.zoom = INITIAL_FOUR_VIEW_ZOOM
        self.image_fusion_view_axial.update_view(zoom_change=True)
        self.image_fusion_view_sagittal.update_view(zoom_change=True)
        self.image_fusion_view_coronal.update_view(zoom_change=True)

        self.image_fusion_four_views = QWidget()
        self.image_fusion_four_views_layout = QGridLayout()
        for i in range(2):
            self.image_fusion_four_views_layout.setColumnStretch(i, 1)
            self.image_fusion_four_views_layout.setRowStretch(i, 1)
        self.image_fusion_four_views_layout.addWidget(
            self.image_fusion_view_axial, 0, 0)
        self.image_fusion_four_views_layout.addWidget(
            self.image_fusion_view_sagittal, 0, 1)
        self.image_fusion_four_views_layout.addWidget(
            self.image_fusion_view_coronal, 1, 0)

        self.image_fusion_four_views_layout.addWidget(
            self.image_fusion_roi_transfer_option_view, 1, 1
        )
        self.image_fusion_four_views.setLayout(
            self.image_fusion_four_views_layout)

        self.image_fusion_view.addWidget(self.image_fusion_four_views)
        self.image_fusion_view.addWidget(self.image_fusion_single_view)
        self.image_fusion_view.setCurrentWidget(self.image_fusion_four_views)

        # Add Image Fusion Tab
        self.right_panel.addTab(self.image_fusion_view, "Image Fusion")
        self.right_panel.setCurrentWidget(self.image_fusion_view)

        # Update the Add On Option GUI
        self.add_on_options_controller.update_ui()

    def perform_suv2roi(self):
        """
        Performs the SUV2ROI process.
        """
        # Get patient weight - needs to run first as GUI cannot run in
        # threads, like the ProgressBar
        patient_dict_container = PatientDictContainer()
        dataset = patient_dict_container.dataset[0]
        self.suv2roi.get_patient_weight(dataset)
        if self.suv2roi.patient_weight is None:
            return

        # Start the SUV2ROI process
        self.suv2roi_progress_window.start(self.suv2roi.start_conversion)

    def on_loaded_suv2roi(self):
        """
        Called when progress bar has finished. Closes the progress
        window and refreshes the main screen.
        """
        if self.suv2roi.suv2roi_status:
            patient_dict_container = PatientDictContainer()
            self.structures_tab.fixed_container_structure_modified((
                patient_dict_container.get('dataset_rtss'), {"draw": None}))
        else:
            # Alert user that SUV2ROI failed and for what reason
            if self.suv2roi.failure_reason == "UNIT":
                failure_reason = \
                    "PET units are not Bq/mL. OnkoDICOM can currently only\n" \
                    "perform SUV2ROI on PET images stored in these units."
            elif self.suv2roi.failure_reason == "DECY":
                failure_reason = \
                    "PET is not decay corrected. OnkoDICOM can currently " \
                    "only\nperform SUV2ROI on PET images that are decay " \
                    "corrected."
            else:
                failure_reason = "The SUV2ROI process has failed."
            button_reply = \
                QtWidgets.QMessageBox(
                    QtWidgets.QMessageBox.Icon.Warning,
                    "SUV2ROI Failed",
                    failure_reason,
                    QtWidgets.QMessageBox.StandardButton.Ok, self)
            button_reply.button(
                QtWidgets.QMessageBox.StandardButton.Ok).setStyleSheet(
                self.stylesheet)
            button_reply.exec_()

        # Close progress window
        self.suv2roi_progress_window.close()
Exemple #2
0
class UIOpenPatientWindow(object):
    patient_info_initialized = QtCore.pyqtSignal(object)

    def setup_ui(self, open_patient_window_instance):
        if platform.system() == 'Darwin':
            self.stylesheet_path = "src/res/stylesheet.qss"
        else:
            self.stylesheet_path = "src/res/stylesheet-win-linux.qss"
        stylesheet = open(resource_path(self.stylesheet_path)).read()
        window_icon = QIcon()
        window_icon.addPixmap(
            QPixmap(resource_path("src/res/images/icon.ico")), QIcon.Normal,
            QIcon.Off)
        open_patient_window_instance.setObjectName("OpenPatientWindowInstance")
        open_patient_window_instance.setWindowIcon(window_icon)
        open_patient_window_instance.resize(840, 530)

        # Create a vertical box for containing the other elements and layouts
        self.open_patient_window_instance_vertical_box = QVBoxLayout()
        self.open_patient_window_instance_vertical_box.setObjectName(
            "OpenPatientWindowInstanceVerticalBox")

        # Create a label to prompt the user to enter the path to the directory that contains the DICOM files
        self.open_patient_directory_prompt = QLabel()
        self.open_patient_directory_prompt.setObjectName(
            "OpenPatientDirectoryPrompt")
        self.open_patient_directory_prompt.setAlignment(Qt.AlignLeft)
        self.open_patient_window_instance_vertical_box.addWidget(
            self.open_patient_directory_prompt)

        # Create a horizontal box to hold the input box for the directory and the choose button
        self.open_patient_directory_input_horizontal_box = QHBoxLayout()
        self.open_patient_directory_input_horizontal_box.setObjectName(
            "OpenPatientDirectoryInputHorizontalBox")
        # Create a textbox to contain the path to the directory that contains the DICOM files
        self.open_patient_directory_input_box = UIOpenPatientWindowDragAndDropEvent(
            self)

        self.open_patient_directory_input_box.setObjectName(
            "OpenPatientDirectoryInputBox")
        self.open_patient_directory_input_box.setSizePolicy(
            QSizePolicy(QSizePolicy.MinimumExpanding,
                        QSizePolicy.MinimumExpanding))
        self.open_patient_directory_input_box.returnPressed.connect(
            self.scan_directory_for_patient)
        self.open_patient_directory_input_horizontal_box.addWidget(
            self.open_patient_directory_input_box)

        # Create a choose button to open the file dialog
        self.open_patient_directory_choose_button = QPushButton()
        self.open_patient_directory_choose_button.setObjectName(
            "OpenPatientDirectoryChooseButton")
        self.open_patient_directory_choose_button.setSizePolicy(
            QSizePolicy(QSizePolicy.MinimumExpanding,
                        QSizePolicy.MinimumExpanding))
        self.open_patient_directory_choose_button.resize(
            self.open_patient_directory_choose_button.sizeHint().width(),
            self.open_patient_directory_input_box.height())
        self.open_patient_directory_choose_button.setCursor(
            QtGui.QCursor(QtCore.Qt.PointingHandCursor))
        self.open_patient_directory_input_horizontal_box.addWidget(
            self.open_patient_directory_choose_button)
        self.open_patient_directory_choose_button.clicked.connect(
            self.choose_button_clicked)
        # Create a widget to hold the input fields
        self.open_patient_directory_input_widget = QWidget()
        self.open_patient_directory_input_horizontal_box.setStretch(0, 4)
        self.open_patient_directory_input_widget.setLayout(
            self.open_patient_directory_input_horizontal_box)
        self.open_patient_window_instance_vertical_box.addWidget(
            self.open_patient_directory_input_widget)

        # Create a horizontal box to hold the stop button and direction to the user on where to select the patient
        self.open_patient_appear_prompt_and_stop_horizontal_box = QHBoxLayout()
        self.open_patient_appear_prompt_and_stop_horizontal_box.setObjectName(
            "OpenPatientAppearPromptAndStopHorizontalBox")
        # Create a label to show direction on where the files will appear
        self.open_patient_directory_appear_prompt = QLabel()
        self.open_patient_directory_appear_prompt.setObjectName(
            "OpenPatientDirectoryAppearPrompt")
        self.open_patient_directory_appear_prompt.setAlignment(Qt.AlignLeft)
        self.open_patient_appear_prompt_and_stop_horizontal_box.addWidget(
            self.open_patient_directory_appear_prompt)
        self.open_patient_appear_prompt_and_stop_horizontal_box.addStretch(1)
        # Create a button to stop searching
        self.open_patient_window_stop_button = QPushButton()
        self.open_patient_window_stop_button.setObjectName(
            "OpenPatientWindowStopButton")
        self.open_patient_window_stop_button.setSizePolicy(
            QSizePolicy(QSizePolicy.MinimumExpanding,
                        QSizePolicy.MinimumExpanding))
        self.open_patient_window_stop_button.resize(
            self.open_patient_window_stop_button.sizeHint().width(),
            self.open_patient_window_stop_button.sizeHint().height())
        self.open_patient_window_stop_button.setCursor(
            QtGui.QCursor(QtCore.Qt.PointingHandCursor))
        self.open_patient_window_stop_button.clicked.connect(
            self.stop_button_clicked)
        self.open_patient_window_stop_button.setProperty(
            "QPushButtonClass", "fail-button")
        self.open_patient_window_stop_button.setVisible(
            False)  # Button doesn't show until a search commences
        self.open_patient_appear_prompt_and_stop_horizontal_box.addWidget(
            self.open_patient_window_stop_button)
        # Create a widget to hold the layout
        self.open_patient_appear_prompt_and_stop_widget = QWidget()
        self.open_patient_appear_prompt_and_stop_widget.setLayout(
            self.open_patient_appear_prompt_and_stop_horizontal_box)
        self.open_patient_window_instance_vertical_box.addWidget(
            self.open_patient_appear_prompt_and_stop_widget)

        # Create a tree view list to list out all patients in the directory selected above
        self.open_patient_window_patients_tree = QTreeWidget()
        self.open_patient_window_patients_tree.setObjectName(
            "OpenPatientWindowPatientsTree")
        self.open_patient_window_patients_tree.setSizePolicy(
            QSizePolicy(QSizePolicy.MinimumExpanding,
                        QSizePolicy.MinimumExpanding))
        self.open_patient_window_patients_tree.resize(
            self.open_patient_window_patients_tree.sizeHint().width(),
            self.open_patient_window_patients_tree.sizeHint().height())
        self.open_patient_window_patients_tree.setHeaderHidden(True)
        self.open_patient_window_patients_tree.setHeaderLabels([""])
        self.open_patient_window_instance_vertical_box.addWidget(
            self.open_patient_window_patients_tree)

        # Create a label to show what would happen if they select the patient
        self.open_patient_directory_result_label = QtWidgets.QLabel()
        self.open_patient_directory_result_label.setObjectName(
            "OpenPatientDirectoryResultLabel")
        self.open_patient_directory_result_label.setAlignment(Qt.AlignLeft)
        self.open_patient_window_instance_vertical_box.addWidget(
            self.open_patient_directory_result_label)

        # Create a horizontal box to hold the Cancel and Open button
        self.open_patient_window_patient_open_actions_horizontal_box = QHBoxLayout(
        )
        self.open_patient_window_patient_open_actions_horizontal_box.setObjectName(
            "OpenPatientWindowPatientOpenActionsHorizontalBox")
        self.open_patient_window_patient_open_actions_horizontal_box.addStretch(
            1)
        # Add a button to go back/exit from the application
        self.open_patient_window_exit_button = QPushButton()
        self.open_patient_window_exit_button.setObjectName(
            "OpenPatientWindowExitButton")
        self.open_patient_window_exit_button.setSizePolicy(
            QSizePolicy(QSizePolicy.MinimumExpanding,
                        QSizePolicy.MinimumExpanding))
        self.open_patient_window_exit_button.resize(
            self.open_patient_window_stop_button.sizeHint().width(),
            self.open_patient_window_stop_button.sizeHint().height())
        self.open_patient_window_exit_button.setCursor(
            QtGui.QCursor(QtCore.Qt.PointingHandCursor))
        self.open_patient_window_exit_button.clicked.connect(
            self.exit_button_clicked)
        self.open_patient_window_exit_button.setProperty(
            "QPushButtonClass", "fail-button")
        self.open_patient_window_patient_open_actions_horizontal_box.addWidget(
            self.open_patient_window_exit_button)

        # Add a button to confirm opening of the patient
        self.open_patient_window_confirm_button = QPushButton()
        self.open_patient_window_confirm_button.setObjectName(
            "OpenPatientWindowConfirmButton")
        self.open_patient_window_confirm_button.setSizePolicy(
            QSizePolicy(QSizePolicy.MinimumExpanding,
                        QSizePolicy.MinimumExpanding))
        self.open_patient_window_confirm_button.resize(
            self.open_patient_window_confirm_button.sizeHint().width(),
            self.open_patient_window_confirm_button.sizeHint().height())
        self.open_patient_window_confirm_button.setCursor(
            QtGui.QCursor(QtCore.Qt.PointingHandCursor))
        self.open_patient_window_confirm_button.clicked.connect(
            self.confirm_button_clicked)
        self.open_patient_window_confirm_button.setProperty(
            "QPushButtonClass", "success-button")
        self.open_patient_window_patient_open_actions_horizontal_box.addWidget(
            self.open_patient_window_confirm_button)

        # Create a widget to house all of the actions button for open patient window
        self.open_patient_window_patient_open_actions_widget = QWidget()
        self.open_patient_window_patient_open_actions_widget.setLayout(
            self.open_patient_window_patient_open_actions_horizontal_box)
        self.open_patient_window_instance_vertical_box.addWidget(
            self.open_patient_window_patient_open_actions_widget)

        # Set the vertical box fourth element, the tree view, to stretch out as far as possible
        self.open_patient_window_instance_vertical_box.setStretch(
            3, 4)  # Stretch the treeview out as far as possible
        self.open_patient_window_instance_central_widget = QWidget()
        self.open_patient_window_instance_central_widget.setObjectName(
            "OpenPatientWindowInstanceCentralWidget")
        self.open_patient_window_instance_central_widget.setLayout(
            self.open_patient_window_instance_vertical_box)

        # Create threadpool for multithreading
        self.threadpool = QThreadPool()
        print("Multithreading with maximum %d threads" %
              self.threadpool.maxThreadCount())
        # Create interrupt event for stopping the directory search
        self.interrupt_flag = threading.Event()

        # Bind all texts into the buttons and labels
        self.retranslate_ui(open_patient_window_instance)
        # Set the central widget, ready for display
        open_patient_window_instance.setCentralWidget(
            self.open_patient_window_instance_central_widget)

        # Set the current stylesheet to the instance and connect it back to the caller through slot
        open_patient_window_instance.setStyleSheet(stylesheet)
        QtCore.QMetaObject.connectSlotsByName(open_patient_window_instance)

    def retranslate_ui(self, open_patient_window_instance):
        _translate = QtCore.QCoreApplication.translate
        open_patient_window_instance.setWindowTitle(
            _translate("OpenPatientWindowInstance",
                       "OnkoDICOM - Select Patient"))
        self.open_patient_directory_prompt.setText(
            _translate(
                "OpenPatientWindowInstance",
                "Choose the path of the folder containing DICOM files to load Patient's details:"
            ))
        self.open_patient_directory_input_box.setPlaceholderText(
            _translate(
                "OpenPatientWindowInstance",
                "Enter DICOM Files Path (For example, C:\path\\to\your\DICOM\Files)"
            ))
        self.open_patient_directory_choose_button.setText(
            _translate("OpenPatientWindowInstance", "Choose"))
        self.open_patient_directory_appear_prompt.setText(
            _translate(
                "OpenPatientWindowInstance",
                "Patient File directory shown below once file path chosen. Please select the file(s) you want to open:"
            ))
        self.open_patient_directory_result_label.setText(
            "The selected directory(s) above will be opened in the OnkoDICOM program."
        )
        self.open_patient_window_stop_button.setText(
            _translate("OpenPatientWindowInstance", "Stop Search"))
        self.open_patient_window_exit_button.setText(
            _translate("OpenPatientWindowInstance", "Exit"))
        self.open_patient_window_confirm_button.setText(
            _translate("OpenPatientWindowInstance", "Confirm"))

    def exit_button_clicked(self):
        QCoreApplication.exit(0)

    def scan_directory_for_patient(self):
        self.filepath = self.open_patient_directory_input_box.text()
        # Proceed if a folder was selected
        if self.filepath != "":
            # Update the QTreeWidget to reflect data being loaded
            # First, clear the widget of any existing data
            self.open_patient_window_patients_tree.clear()

            # Next, update the tree widget
            self.open_patient_window_patients_tree.addTopLevelItem(
                QTreeWidgetItem(["Loading selected directory..."]))

            # The choose button is disabled until the thread finishes executing
            self.open_patient_directory_choose_button.setEnabled(False)

            # Reveals the Stop Search button for the duration of the search
            self.open_patient_window_stop_button.setVisible(True)

            # The interrupt flag is then un-set if a previous search has been stopped.
            self.interrupt_flag.clear()

            # Then, create a new thread that will load the selected folder
            worker = Worker(DICOMDirectorySearch.get_dicom_structure,
                            self.filepath,
                            self.interrupt_flag,
                            progress_callback=True)
            worker.signals.result.connect(self.on_search_complete)
            worker.signals.progress.connect(self.search_progress)

            # Execute the thread
            self.threadpool.start(worker)

    def choose_button_clicked(self):
        """
        Executes when the choose button is clicked.
        Gets filepath from the user and loads all files and subdirectories.
        """
        # Get folder path from pop up dialog box
        self.filepath = QtWidgets.QFileDialog.getExistingDirectory(
            None, 'Select patient folder...', '')
        self.open_patient_directory_input_box.setText(self.filepath)
        self.scan_directory_for_patient()

    def stop_button_clicked(self):
        self.interrupt_flag.set()

    def search_progress(self, progress_update):
        """
        Current progress of the file search.
        """
        self.open_patient_window_patients_tree.clear()
        self.open_patient_window_patients_tree.addTopLevelItem(
            QTreeWidgetItem([
                "Loading selected directory... (%s files searched)" %
                progress_update
            ]))

    def on_search_complete(self, dicom_structure):
        """
        Executes once the directory search is complete.
        :param dicom_structure: DICOMStructure object constructed by the directory search.
        """
        self.open_patient_directory_choose_button.setEnabled(True)
        self.open_patient_window_stop_button.setVisible(False)
        self.open_patient_window_patients_tree.clear()

        if dicom_structure is None:  # dicom_structure will be None if function was interrupted.
            return

        for patient_item in dicom_structure.get_tree_items_list():
            self.open_patient_window_patients_tree.addTopLevelItem(
                patient_item)

        if len(dicom_structure.patients) == 0:
            QMessageBox.about(self, "No files found",
                              "Selected directory contains no DICOM files.")

    def confirm_button_clicked(self):
        """
        Begins loading of the selected files.
        """
        selected_files = []
        for item in self.get_checked_leaves():
            selected_files += item.dicom_object.get_files()

        if len(selected_files) > 0:
            self.progress_window = ProgressWindow(
                self,
                QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowCloseButtonHint)
            self.progress_window.signal_loaded.connect(self.on_loaded)
            self.progress_window.signal_error.connect(self.on_loading_error)

            self.progress_window.start_loading(selected_files)
            self.progress_window.exec_()
        else:
            QMessageBox.about(self, "Unable to open selection",
                              "No files selected.")

    def on_loaded(self, results):
        """
        Executes when the progress bar finishes loaded the selected files.
        """
        if results[0] is True:  # Will be NoneType if loading was interrupted.
            self.patient_info_initialized.emit(
                results[1])  # Emits the progress window.

    def on_loading_error(self, error_code):
        """
        Error handling for progress window.
        """
        if error_code == 0:
            QMessageBox.about(
                self.progress_window, "Unable to open selection",
                "Selected files cannot be opened as they are not a DICOM-RT set."
            )
            self.progress_window.close()
        elif error_code == 1:
            QMessageBox.about(
                self.progress_window, "Unable to open selection",
                "Selected files cannot be opened as they contain unsupported DICOM classes."
            )
            self.progress_window.close()

    def get_checked_leaves(self):
        """
        :return: A list of all QTreeWidgetItems in the QTreeWidget that are both leaves and checked.
        """
        checked_items = []

        def recurse(parent_item: QTreeWidgetItem):
            for i in range(parent_item.childCount()):
                child = parent_item.child(i)
                grand_children = child.childCount()
                if grand_children > 0:
                    recurse(child)
                else:
                    if child.checkState(0) == Qt.Checked:
                        checked_items.append(child)

        recurse(self.open_patient_window_patients_tree.invisibleRootItem())
        return checked_items