Exemple #1
0
    def __init__(self, text):
        """
        Initialises the widget
        :param text: the window selected
        """
        super(Windowing, self).__init__()

        self.pt_ct_dict_container = PTCTDictContainer()
        self.moving_dict_container = MovingDictContainer()

        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.setWindowIcon(window_icon)
        self.setStyleSheet(self.stylesheet)

        self.text = text

        self.layout = QVBoxLayout()
        self.buttons = QHBoxLayout()

        self.setWindowTitle("Select Views")

        self.label = QLabel("Select views to apply windowing to:")
        self.normal = QtWidgets.QCheckBox("DICOM View")
        self.pet = QtWidgets.QCheckBox("PET/CT: PET")
        self.ct = QtWidgets.QCheckBox("PET/CT: CT")
        self.fusion = QtWidgets.QCheckBox("Image Fusion")
        self.confirm = QtWidgets.QPushButton("Confirm")
        self.cancel = QtWidgets.QPushButton("Cancel")

        self.confirm.clicked.connect(self.confirmed)
        self.confirm.setProperty("QPushButtonClass", "success-button")
        self.cancel.clicked.connect(self.exit_button)
        self.cancel.setProperty("QPushButtonClass", "fail-button")

        self.buttons.addWidget(self.cancel)
        self.buttons.addWidget(self.confirm)

        self.layout.addWidget(self.label)
        self.layout.addWidget(self.normal)
        if not self.pt_ct_dict_container.is_empty():
            self.layout.addWidget(self.pet)
            self.layout.addWidget(self.ct)
        if not self.moving_dict_container.is_empty():
            self.layout.addWidget(self.fusion)
        self.layout.addLayout(self.buttons)

        self.setLayout(self.layout)
 def windowing_handler(self, state, text):
     """
     Function triggered when a window is selected from the menu.
     :param state: Variable not used. Present to be able to use a
         lambda function.
     :param text: The name of the window selected.
     """
     ptct = PTCTDictContainer()
     mvd = MovingDictContainer()
     if ptct.is_empty() and mvd.is_empty():
         windowing_model(text, [True, False, False, False])
         self.update_views()
     else:
         self.windowing_window.set_window(text)
         self.windowing_window.show()
    def cleanup_image_fusion(self):
        # Explicity destroy objects - the purpose of this is to clear
        # any image fusion tabs that have been used previously.
        # Try-catch in the event user has not prompted image-fusion.
        try:
            del self.image_fusion_view_coronal
            del self.image_fusion_view_sagittal
            del self.image_fusion_view_axial
            del self.image_fusion_four_views_layout
            del self.image_fusion_four_views
            del self.image_fusion_single_view
            del self.image_fusion_view
        except:
            pass

        moving_dict_container = MovingDictContainer()
        moving_dict_container.clear()
Exemple #4
0
def create_roi(rtss,
               roi_name,
               roi_list,
               rt_roi_interpreted_type="ORGAN",
               rtss_owner="PATIENT"):
    """
    Create new contours of an ROI to rtss :param rtss: dataset of RTSS
    :param roi_name: ROIName :param roi_list: the list of contours to be
    added to the rtss. Each element consists of coordinates of pixels for
    new contour and data set of selected DICOM image file. :param
    rt_roi_interpreted_type: the interpreted type of the new ROI :param
    rtss_owner: the type of patient dict container (either PATIENT or
    MOVING) caller wants to create ROI to :return: rtss, with added ROI
    """
    if rtss_owner == "MOVING":
        patient_dict_container = MovingDictContainer()
    else:
        patient_dict_container = PatientDictContainer()

    existing_rois = patient_dict_container.get("rois")
    roi_exists = False

    # This is for adding a new slice to an already existing ROI.
    # For Future Development.
    # Check to see if the ROI already exists
    for key, value in existing_rois.items():
        if value["name"] == roi_name:
            roi_exists = True
    for roi_info in roi_list:
        data_set = roi_info['ds']
        roi_coordinates = roi_info['coords']
        if not roi_exists:
            rtss = add_new_roi(rtss, roi_name, roi_coordinates, data_set,
                               rt_roi_interpreted_type)
            roi_exists = True
        else:
            # Add contour image data to existing ROI
            rtss = add_to_roi(rtss, roi_name, roi_coordinates, data_set)

    return rtss
    def setup_ui(self, transfer_roi_window_instance,
                 signal_roi_transferred_to_fixed_container,
                 signal_roi_transferred_to_moving_container):
        self.patient_dict_container = PatientDictContainer()
        self.moving_dict_container = MovingDictContainer()
        self.fixed_image_initial_rois = self.patient_dict_container.get("rois")
        self.moving_image_initial_rois = self.moving_dict_container.get("rois")
        self.transfer_roi_window_instance = transfer_roi_window_instance
        self.signal_roi_transferred_to_fixed_container = \
            signal_roi_transferred_to_fixed_container
        self.signal_roi_transferred_to_moving_container = \
            signal_roi_transferred_to_moving_container
        self.fixed_to_moving_rois = {}
        self.moving_to_fixed_rois = {}
        self.add_suffix = True
        self.progress_window = ProgressWindow(
            self, QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowCloseButtonHint)
        self.progress_window.setFixedSize(250, 100)
        self.progress_window.signal_loaded \
            .connect(self.onTransferRoiFinished)
        self.progress_window.signal_error.connect(self.onTransferRoiError)

        self.init_layout()
    def get_patients_info(self):
        """
        Retrieve the patient's study description of the fixed image
        and for the moving image (if it exists).
        """
        patient_dict_container = PatientDictContainer()
        if not patient_dict_container.is_empty():
            filename = patient_dict_container.filepaths[0]
            dicom_tree_slice = DicomTree(filename)
            dict_tree = dicom_tree_slice.dict
            try:
                self.fixed_image = dict_tree["Series Instance UID"][0]
            except:
                self.fixed_image = ""
                self.warning_label.setText(
                    'Couldn\'t find the series instance '
                    'UID for the Fixed Image.')

        moving_dict_container = MovingDictContainer()
        if not moving_dict_container.is_empty():
            filename = moving_dict_container.filepaths[0]
            dicom_tree_slice = DicomTree(filename)
            dict_tree = dicom_tree_slice.dict
            try:
                self.moving_image = dict_tree["Series Instance UID"][0]
            except:
                self.moving_image = ""
                self.warning_label.setText(
                    'Couldn\'t find the series instance '
                    'UID for the Moving Image.')

        if moving_dict_container.is_empty() and self.moving_image != "":
            self.moving_image = ""

        self.fixed_image_placeholder.setText(str(self.fixed_image))
        self.moving_image_placeholder.setText(str(self.moving_image))
Exemple #7
0
def read_images_for_fusion(level=0, window=0):
    """
    Performs initial image fusion, this is by converting the old and
    new images for transformations into SITK object. Images are co-registered 
    using SITK library. Images and SITK.CompositeTransformation objects are 
    added to the patient dataset.
    
    Args:
        level(int): midpoint of window
        window(Any): range of values, should at least contain low bound and 
        high bound
    """
    patient_dict_container = PatientDictContainer()
    moving_dict_container = MovingDictContainer()
    if level == 0 or window == 0:
        level = patient_dict_container.get("level")
        window = patient_dict_container.get("window")

    amount = len(patient_dict_container.filepaths)
    orig_fusion_list = []

    for i in range(amount):
        try:
            orig_fusion_list.append(patient_dict_container.filepaths[i])
        except KeyError:
            continue

    orig_image = sitk.ReadImage(orig_fusion_list)
    patient_dict_container.set("sitk_original", orig_image)

    amount = len(moving_dict_container.filepaths)
    new_fusion_list = []

    for i in range(amount):
        try:
            new_fusion_list.append(moving_dict_container.filepaths[i])
        except KeyError:
            continue

    new_image = sitk.ReadImage(new_fusion_list)
    moving_dict_container.set("sitk_moving", new_image)

    create_fused_model(orig_image, new_image)
    color_axial, color_sagittal, color_coronal, tfm = \
        get_fused_window(level, window)

    patient_dict_container.set("color_axial", color_axial)
    patient_dict_container.set("color_sagittal", color_sagittal)
    patient_dict_container.set("color_coronal", color_coronal)
    moving_dict_container.set("tfm", tfm)
Exemple #8
0
def windowing_model(text, init):
    """
    Function triggered when a window is selected from the menu.
    :param text: The name of the window selected.
    :param init: list of bool to determine which views are chosen
    """
    patient_dict_container = PatientDictContainer()
    moving_dict_container = MovingDictContainer()
    pt_ct_dict_container = PTCTDictContainer()

    # Get the values for window and level from the dict
    windowing_limits = patient_dict_container.get("dict_windowing")[text]

    # Set window and level to the new values
    window = windowing_limits[0]
    level = windowing_limits[1]

    # Update the dictionary of pixmaps with the update window and
    # level values
    if init[0]:
        pixel_values = patient_dict_container.get("pixel_values")
        pixmap_aspect = patient_dict_container.get("pixmap_aspect")
        pixmaps_axial, pixmaps_coronal, pixmaps_sagittal = \
            get_pixmaps(pixel_values, window, level, pixmap_aspect)

        patient_dict_container.set("pixmaps_axial", pixmaps_axial)
        patient_dict_container.set("pixmaps_coronal", pixmaps_coronal)
        patient_dict_container.set("pixmaps_sagittal", pixmaps_sagittal)
        patient_dict_container.set("window", window)
        patient_dict_container.set("level", level)

    # Update CT
    if init[2]:
        ct_pixel_values = pt_ct_dict_container.get("ct_pixel_values")
        ct_pixmap_aspect = pt_ct_dict_container.get("ct_pixmap_aspect")
        ct_pixmaps_axial, ct_pixmaps_coronal, ct_pixmaps_sagittal = \
            get_pixmaps(ct_pixel_values, window, level, ct_pixmap_aspect,
                        fusion=True)

        pt_ct_dict_container.set("ct_pixmaps_axial", ct_pixmaps_axial)
        pt_ct_dict_container.set("ct_pixmaps_coronal", ct_pixmaps_coronal)
        pt_ct_dict_container.set("ct_pixmaps_sagittal", ct_pixmaps_sagittal)
        pt_ct_dict_container.set("ct_window", window)
        pt_ct_dict_container.set("ct_level", level)

    # Update PT
    if init[1]:
        pt_pixel_values = pt_ct_dict_container.get("pt_pixel_values")
        pt_pixmap_aspect = pt_ct_dict_container.get("pt_pixmap_aspect")
        pt_pixmaps_axial, pt_pixmaps_coronal, pt_pixmaps_sagittal = \
            get_pixmaps(pt_pixel_values, window, level, pt_pixmap_aspect,
                        fusion=True, color="Heat")

        pt_ct_dict_container.set("pt_pixmaps_axial", pt_pixmaps_axial)
        pt_ct_dict_container.set("pt_pixmaps_coronal", pt_pixmaps_coronal)
        pt_ct_dict_container.set("pt_pixmaps_sagittal", pt_pixmaps_sagittal)
        pt_ct_dict_container.set("pt_window", window)
        pt_ct_dict_container.set("pt_level", level)

    # Update Fusion
    if init[3]:
        fusion_axial, fusion_coronal, fusion_sagittal, tfm = \
            get_fused_window(level, window)
        patient_dict_container.set("color_axial", fusion_axial)
        patient_dict_container.set("color_coronal", fusion_coronal)
        patient_dict_container.set("color_sagittal", fusion_sagittal)
        moving_dict_container.set("tfm", tfm)
class UITransferROIWindow:
    def setup_ui(self, transfer_roi_window_instance,
                 signal_roi_transferred_to_fixed_container,
                 signal_roi_transferred_to_moving_container):
        self.patient_dict_container = PatientDictContainer()
        self.moving_dict_container = MovingDictContainer()
        self.fixed_image_initial_rois = self.patient_dict_container.get("rois")
        self.moving_image_initial_rois = self.moving_dict_container.get("rois")
        self.transfer_roi_window_instance = transfer_roi_window_instance
        self.signal_roi_transferred_to_fixed_container = \
            signal_roi_transferred_to_fixed_container
        self.signal_roi_transferred_to_moving_container = \
            signal_roi_transferred_to_moving_container
        self.fixed_to_moving_rois = {}
        self.moving_to_fixed_rois = {}
        self.add_suffix = True
        self.progress_window = ProgressWindow(
            self, QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowCloseButtonHint)
        self.progress_window.setFixedSize(250, 100)
        self.progress_window.signal_loaded \
            .connect(self.onTransferRoiFinished)
        self.progress_window.signal_error.connect(self.onTransferRoiError)

        self.init_layout()

    def retranslate_ui(self, transfer_roi_window_instance):
        _translate = QtCore.QCoreApplication.translate
        transfer_roi_window_instance.setWindowTitle(
            _translate("TransferRoiWindowInstance",
                       "OnkoDICOM - Transfer Region of Interest"))
        self.add_suffix_checkbox.setText(
            _translate("AddSuffixCheckBox", "Add Suffix"))
        self.patient_A_label.setText(
            _translate("PatientAROILabel", "First Image Set ROIs"))
        self.patient_B_label.setText(
            _translate("PatientBROILabel", "Second Image Set ROIs"))
        self.transfer_all_rois_to_patient_B_button.setText(
            _translate("ROITransferToBButton", "All"))
        self.transfer_all_rois_to_patient_A_button.setText(
            _translate("ROITransferToAButton", "All"))
        self.save_button.setText(_translate("SaveButton", "Save"))
        self.reset_button.setText(_translate("ResetButton", "Reset"))

    def init_layout(self):
        """
        Initialize the layout for the Transfer ROI Window.
        """
        if platform.system() == 'Darwin':
            self.stylesheet_path = "res/stylesheet.qss"
        else:
            self.stylesheet_path = "res/stylesheet-win-linux.qss"
        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.transfer_roi_window_instance.setObjectName(
            "TransferRoiWindowInstance")
        self.transfer_roi_window_instance.setWindowIcon(window_icon)

        # Creating a grid layout to hold all elements
        self.transfer_roi_window_grid_layout = QGridLayout()
        self.transfer_roi_window_grid_layout.setColumnStretch(0, 1)
        self.transfer_roi_window_grid_layout.setColumnStretch(1, 1)
        self.transfer_roi_window_grid_layout.setColumnStretch(2, 1)

        self.init_patient_labels()
        self.init_transfer_arrow_buttons()
        self.init_patient_A_initial_roi_list()
        self.init_patient_B_rois_to_A_layout()
        self.init_patient_A_rois_to_B_layout()
        self.init_patient_B_initial_roi_list()
        self.init_add_suffix_checkbox()
        self.init_save_and_reset_button_layout()

        # Create a new central widget to hold the grid layout
        self.transfer_roi_window_instance_central_widget = QWidget()
        self.transfer_roi_window_instance_central_widget.setLayout(
            self.transfer_roi_window_grid_layout)
        self.retranslate_ui(self.transfer_roi_window_instance)
        self.transfer_roi_window_instance.setStyleSheet(stylesheet)
        self.transfer_roi_window_instance.setCentralWidget(
            self.transfer_roi_window_instance_central_widget)
        QtCore.QMetaObject.connectSlotsByName(
            self.transfer_roi_window_instance)

    def init_transfer_arrow_buttons(self):
        """
        Initialize the layout for arrow buttons

        """
        self.transfer_all_rois_to_patient_B_button = QPushButton()
        self.transfer_all_rois_to_patient_B_button.setObjectName(
            "ROITransferToBButton")

        transfer_all_rois_to_patient_B_icon = QtGui.QIcon()
        transfer_all_rois_to_patient_B_icon.addPixmap(
            QtGui.QPixmap(
                resource_path('res/images/btn-icons/forward_slide_icon.png')),
            QtGui.QIcon.Normal, QtGui.QIcon.On)
        self.transfer_all_rois_to_patient_B_button \
            .setIcon(transfer_all_rois_to_patient_B_icon)
        self.transfer_all_rois_to_patient_B_button.clicked.connect(
            self.transfer_all_rois_to_patient_B_button_clicked)
        self.transfer_roi_window_grid_layout.addWidget(
            self.transfer_all_rois_to_patient_B_button, 1, 1)

        self.transfer_all_rois_to_patient_A_button = QPushButton()
        self.transfer_all_rois_to_patient_A_button.setObjectName(
            "ROITransferToAButton")
        self.transfer_all_rois_to_patient_A_button.setMaximumWidth(100)
        transfer_all_rois_to_patient_A_icon = QtGui.QIcon()
        transfer_all_rois_to_patient_A_icon.addPixmap(
            QtGui.QPixmap(
                resource_path('res/images/btn-icons/backward_slide_icon.png')),
            QtGui.QIcon.Normal, QtGui.QIcon.On)
        self.transfer_all_rois_to_patient_A_button \
            .setIcon(transfer_all_rois_to_patient_A_icon)
        self.transfer_all_rois_to_patient_A_button.clicked.connect(
            self.transfer_all_rois_to_patient_A_button_clicked)
        self.transfer_roi_window_grid_layout.addWidget(
            self.transfer_all_rois_to_patient_A_button, 2, 1)

    def transfer_all_rois_to_patient_B_button_clicked(self):
        """
        This function is triggered when the right arrow button is clicked.
        """
        self.fixed_to_moving_rois.clear()
        self.patient_A_rois_to_B_list_widget.clear()

        for i in range(0, len(self.fixed_image_initial_rois)):
            self.patient_A_initial_roi_double_clicked(
                self.patient_A_initial_rois_list_widget.item(i))

    def transfer_all_rois_to_patient_A_button_clicked(self):
        """
        This function is triggered when the left arrow button is clicked.
        """
        self.moving_to_fixed_rois.clear()
        self.patient_B_rois_to_A_list_widget.clear()

        for i in range(0, len(self.moving_image_initial_rois)):
            self.patient_B_initial_roi_double_clicked(
                self.patient_B_initial_rois_list_widget.item(i))

    def init_add_suffix_checkbox(self):
        """
        Initialize the layout for add suffix checkbox
        """
        self.add_suffix_checkbox = QCheckBox()
        self.add_suffix_checkbox.setObjectName("AddSuffixCheckBox")
        self.add_suffix_checkbox.setChecked(self.add_suffix)
        self.add_suffix_checkbox.clicked.connect(
            self.add_suffix_checkbox_clicked)
        self.transfer_roi_window_grid_layout.addWidget(
            self.add_suffix_checkbox, 3, 0)

    def init_patient_labels(self):
        """
        Initialize the layout for two patient labels
        """
        self.patient_A_label = QLabel()
        self.patient_A_label.setObjectName("PatientAROILabel")
        self.patient_A_label.setMinimumHeight(50)
        self.patient_A_label.setAlignment(Qt.AlignCenter)
        self.patient_A_label.setStyleSheet(
            "QLabel { background-color : green; color : white; "
            "font-size: 15pt; font-weight: bold;}")

        self.patient_B_label = QLabel()
        self.patient_B_label.setObjectName("PatientBROILabel")
        self.patient_B_label.setMinimumHeight(50)
        self.patient_B_label.setAlignment(Qt.AlignCenter)
        self.patient_B_label.setStyleSheet(
            "QLabel { background-color : red; color : white; "
            "font-size: 15pt; font-weight: bold;}")

        self.transfer_roi_window_grid_layout.addWidget(self.patient_A_label, 0,
                                                       0)
        self.transfer_roi_window_grid_layout.addWidget(self.patient_B_label, 0,
                                                       2)

    def init_save_and_reset_button_layout(self):
        """
        Initialize the layout for save and reset buttons
        """
        self.reset_and_save_buttons_layout = QHBoxLayout()
        self.reset_button = QPushButton()
        self.reset_button.setObjectName("ResetButton")
        self.reset_button.clicked.connect(self.reset_clicked)
        self.save_button = QPushButton()
        self.save_button.setObjectName("SaveButton")
        self.save_button.setDisabled(True)
        self.save_button.clicked.connect(self.transfer_roi_clicked)

        self.reset_and_save_buttons_layout.setAlignment(Qt.AlignRight)
        self.reset_and_save_buttons_layout.addWidget(self.reset_button)
        self.reset_and_save_buttons_layout.addWidget(self.save_button)

        # Create a widget to hold Reset and Save buttons
        self.reset_and_save_button_central_widget = QWidget()
        self.reset_and_save_button_central_widget.setLayout(
            self.reset_and_save_buttons_layout)

        self.transfer_roi_window_grid_layout.addWidget(
            self.reset_and_save_button_central_widget, 3, 2)

    def add_suffix_checkbox_clicked(self):
        """
        This function is triggered when the add suffix checkbox is clicked
        """
        self.add_suffix = self.add_suffix_checkbox.isChecked()

    def init_patient_B_rois_to_A_layout(self):
        """
        Initialize the layout for transfer rois from B to A container
        """
        # Create scrolling area widget to contain the content.
        self.patient_B_rois_to_A_list_widget = QListWidget(self)
        self.transfer_roi_window_grid_layout \
            .addWidget(self.patient_B_rois_to_A_list_widget, 2, 0)
        self.patient_B_rois_to_A_list_widget.itemDoubleClicked.connect(
            self.patient_B_to_A_rois_double_clicked)

    def init_patient_A_rois_to_B_layout(self):
        """
        Initialize the layout for transfer rois from A to B container
        """
        self.patient_A_rois_to_B_list_widget = QListWidget(self)
        self.transfer_roi_window_grid_layout \
            .addWidget(self.patient_A_rois_to_B_list_widget, 1, 2)
        self.patient_A_rois_to_B_list_widget.itemDoubleClicked.connect(
            self.patient_A_to_B_rois_double_clicked)

    def init_patient_A_initial_roi_list(self):
        """
        Initialize the layout for patient A's roi list
        """
        self.patient_A_initial_rois_list_widget = QListWidget(self)
        self.patient_A_initial_rois_list_widget.itemDoubleClicked.connect(
            self.patient_A_initial_roi_double_clicked)
        for idx in self.fixed_image_initial_rois:
            roi_label = QListWidgetItem(
                self.fixed_image_initial_rois[idx]['name'])
            roi_label.setForeground(Qt.darkGreen)
            roi_label.setData(Qt.UserRole, self.fixed_image_initial_rois[idx])
            self.patient_A_initial_rois_list_widget.addItem(roi_label)
        self.transfer_roi_window_grid_layout.addWidget(
            self.patient_A_initial_rois_list_widget, 1, 0)

    def init_patient_B_initial_roi_list(self):
        """
        Initialize the layout for patient B's roi list
        """
        self.patient_B_initial_rois_list_widget = QListWidget(self)
        self.patient_B_initial_rois_list_widget.itemDoubleClicked.connect(
            self.patient_B_initial_roi_double_clicked)
        for idx in self.moving_image_initial_rois:
            roi_label = QListWidgetItem(
                self.moving_image_initial_rois[idx]['name'])
            roi_label.setForeground(Qt.red)
            roi_label.setData(Qt.UserRole, self.moving_image_initial_rois[idx])

            self.patient_B_initial_rois_list_widget.addItem(roi_label)
        self.transfer_roi_window_grid_layout.addWidget(
            self.patient_B_initial_rois_list_widget, 2, 2)

    def patient_A_to_B_rois_double_clicked(self, item):
        """
        This function is triggered when a roi in "A to B" list is
        double-clicked.
        """
        roi_to_remove = item.data(Qt.UserRole)
        to_delete_value = roi_to_remove['name']
        self.fixed_to_moving_rois.pop(to_delete_value)
        self.patient_A_rois_to_B_list_widget.clear()
        for key, value in self.fixed_to_moving_rois.items():
            roi_label = QListWidgetItem(value)
            roi_label.setForeground(Qt.red)
            roi_label.setData(Qt.UserRole, {'name': key})
            self.patient_A_rois_to_B_list_widget.addItem(roi_label)
        if self.transfer_list_is_empty():
            self.save_button.setDisabled(True)

    def patient_B_to_A_rois_double_clicked(self, item):
        """
        This function is triggered when a roi in "B to A" list is
        double-clicked.
        """
        roi_to_remove = item.data(Qt.UserRole)
        to_delete_value = roi_to_remove['name']
        self.moving_to_fixed_rois.pop(to_delete_value)
        self.patient_B_rois_to_A_list_widget.clear()
        for key, value in self.moving_to_fixed_rois.items():
            roi_label = QListWidgetItem(value)
            roi_label.setForeground(Qt.red)
            roi_label.setData(Qt.UserRole, {'name': key})
            self.patient_B_rois_to_A_list_widget.addItem(roi_label)
        if self.transfer_list_is_empty():
            self.save_button.setDisabled(True)

    def patient_A_initial_roi_double_clicked(self, item):
        """
        This function is triggered when a roi in patient A's roi list is
        double-clicked.
        """
        roi_to_add = item.data(Qt.UserRole)
        transferred_roi_name = roi_to_add['name']

        # If the clicked roi is already transferred, return
        if transferred_roi_name in self.fixed_to_moving_rois.keys():
            QMessageBox.about(self, "Transfer Failed",
                              "Chosen ROI has already been transferred!")
            return
        # Create a set to store all current roi names in target patient
        # including both initial rois name and added roi names so far
        patient_B_initial_roi_name_list = set()

        for item in self.fixed_to_moving_rois.values():
            patient_B_initial_roi_name_list.add(item)
        for idx in self.moving_image_initial_rois:
            patient_B_initial_roi_name_list.add(
                self.moving_image_initial_rois[idx]['name'])

        # Check if clicked roi name has duplicate
        # in patient B's initial roi names list
        if transferred_roi_name in patient_B_initial_roi_name_list:
            if self.add_suffix:
                transferred_roi_name = generate_non_duplicated_name(
                    transferred_roi_name, patient_B_initial_roi_name_list)
            else:
                QMessageBox.about(
                    self, "Transfer Failed", "Duplicated ROI name. "
                    "Please consider adding suffix.")
                return

        # Add clicked roi to transferred list
        self.fixed_to_moving_rois[roi_to_add['name']] = transferred_roi_name
        roi_label = QListWidgetItem(transferred_roi_name)
        roi_label.setForeground(Qt.red)
        roi_label.setData(Qt.UserRole, roi_to_add)
        self.patient_A_rois_to_B_list_widget.addItem(roi_label)
        self.save_button.setDisabled(False)

    def patient_B_initial_roi_double_clicked(self, item):
        """
        This function is triggered when a roi in patient B's roi list is
        double-clicked.
        """
        roi_to_add = item.data(Qt.UserRole)
        transferred_roi_name = roi_to_add['name']

        # If the clicked roi is already transferred, return
        if transferred_roi_name in self.moving_to_fixed_rois.keys():
            QMessageBox.about(self, "Transfer Failed",
                              "Chosen ROI has already been transferred!")
            return

        # Create a set to store all current roi names in target patient
        # including both initial rois name and added roi names so far
        patient_A_current_roi_name_list = set()

        for item in self.moving_to_fixed_rois.values():
            patient_A_current_roi_name_list.add(item)

        for idx in self.fixed_image_initial_rois:
            patient_A_current_roi_name_list.add(
                self.fixed_image_initial_rois[idx]['name'])

        # Check if clicked roi name has duplicate in
        # target patient's roi names list
        if transferred_roi_name in patient_A_current_roi_name_list:
            # if add suffix is ticked, iteratively try adding suffix
            # from _A to _Z, stop when no duplicate found
            if self.add_suffix:
                transferred_roi_name = generate_non_duplicated_name(
                    transferred_roi_name, patient_A_current_roi_name_list)
            else:
                QMessageBox.about(
                    self, "Transfer Failed", "Duplicated ROI name. "
                    "Please consider adding suffix.")
                return

        # Add clicked roi to transferred list
        self.moving_to_fixed_rois[roi_to_add['name']] = transferred_roi_name
        roi_label = QListWidgetItem(transferred_roi_name)
        roi_label.setForeground(Qt.red)
        roi_label.setData(Qt.UserRole, roi_to_add)
        self.patient_B_rois_to_A_list_widget.addItem(roi_label)
        self.save_button.setDisabled(False)

    def reset_clicked(self):
        """
        This function is triggered when reset button is clicked.
        """
        self.fixed_to_moving_rois.clear()
        self.moving_to_fixed_rois.clear()
        self.patient_A_rois_to_B_list_widget.clear()
        self.patient_B_rois_to_A_list_widget.clear()
        self.save_button.setDisabled(True)

    def transfer_list_is_empty(self):
        """
        This function is to check if the transfer list is empty
        """
        return len(self.fixed_to_moving_rois) == 0 \
               and len(self.moving_to_fixed_rois) == 0

    def save_clicked(self, interrupt_flag, progress_callback):
        """
        This function is triggered when the save button is clicked. It contains
        all steps in the ROI transferring process.

        :param interrupt_flag: interrupt flag to stop process
        :param progress_callback: signal that receives the current
                                  progress of the loading.
        """
        progress_callback.emit(("Converting images to sitk", 0))

        # check if interrupt flag is set
        if not check_interrupt_flag(interrupt_flag):
            return False

        rtss = self.patient_dict_container.get("dataset_rtss")

        # get sitk for the fixed image
        dicom_image = read_dicom_image_to_sitk(
            self.patient_dict_container.filepaths)

        if not check_interrupt_flag(interrupt_flag):
            return False

        # get array of roi indexes from sitk images
        rois_images_fixed = transform_point_set_from_dicom_struct(
            dicom_image,
            rtss,
            self.fixed_to_moving_rois.keys(),
            spacing_override=None,
            interrupt_flag=interrupt_flag)

        moving_rtss = self.moving_dict_container.get("dataset_rtss")

        if not check_interrupt_flag(interrupt_flag):
            return False

        # get sitk for the moving image
        moving_dicom_image = read_dicom_image_to_sitk(
            self.moving_dict_container.filepaths)

        if not check_interrupt_flag(interrupt_flag):
            return False

        # get array of roi indexes from sitk images
        progress_callback \
            .emit(("Retrieving ROIs from \nboth image sets", 20))

        # check if interrupt flag is set
        if not check_interrupt_flag(interrupt_flag):
            return False

        if moving_rtss:
            rois_images_moving = transform_point_set_from_dicom_struct(
                moving_dicom_image,
                moving_rtss,
                self.moving_to_fixed_rois.keys(),
                spacing_override=None,
                interrupt_flag=interrupt_flag)
        else:
            rois_images_moving = ([], [])

        if not check_interrupt_flag(interrupt_flag):
            return False

        tfm = self.moving_dict_container.get("tfm")

        progress_callback.emit(
            ("Transfering ROIs from moving \nto fixed image set", 40))

        # check if interrupt flag is set
        if not check_interrupt_flag(interrupt_flag):
            return False

        # transform roi from moving_dict to fixed_dict
        self.transfer_rois(self.moving_to_fixed_rois, tfm, dicom_image,
                           rois_images_moving, self.patient_dict_container)

        progress_callback.emit(
            ("Transfering ROIs from fixed \nto moving image set", 60))

        if not check_interrupt_flag(interrupt_flag):
            return False

        # transform roi from moving_dict to fixed_dict
        self.transfer_rois(self.fixed_to_moving_rois, tfm.GetInverse(),
                           moving_dicom_image, rois_images_fixed,
                           self.moving_dict_container)

        progress_callback.emit(("Saving ROIs to RTSS", 80))

        # check if interrupt flag is set
        if not check_interrupt_flag(interrupt_flag):
            return False
        progress_callback.emit(("Reloading window", 90))
        return True

    def transfer_roi_clicked(self):
        """
        telling progress window to start ROI transfer
        """
        self.progress_window.start(self.save_clicked)

    def onTransferRoiError(self, exception):
        """
        This function is triggered when there is an error in the
        ROI transferring process.

        :param exception: exception thrown
        """
        QMessageBox.about(self.progress_window, "Unable to transfer ROIs",
                          "Please check your image set and ROI data.")
        self.progress_window.close()

    def onTransferRoiFinished(self, result):
        """
        This function is triggered when ROI transferring process is finished.
        """
        # emit changed dataset to structure_modified function and
        # auto_save_roi function
        if result[0] is True:
            if len(self.fixed_to_moving_rois) > 0:
                self.signal_roi_transferred_to_moving_container.emit(
                    (self.moving_dict_container.get("dataset_rtss"), {
                        "transfer": None
                    }))
            if len(self.moving_to_fixed_rois) > 0:
                self.signal_roi_transferred_to_fixed_container.emit(
                    (self.patient_dict_container.get("dataset_rtss"), {
                        "transfer": None
                    }))
            self.progress_window.close()
            QMessageBox.about(self.transfer_roi_window_instance, "Saved",
                              "ROIs are successfully transferred!")
        else:
            QMessageBox.about(self.transfer_roi_window_instance, "Cancelled",
                              "ROIs Transfer is cancelled.")
        self.closeWindow()

    def transfer_rois(self, transfer_dict, tfm, reference_image,
                      original_roi_list, patient_dict_container):
        """
        Converting (transferring) ROIs from one image set to another and save
        the transferred rois to rtss.
        :param transfer_dict: dictionary of rois to be transfer.
        key is original roi names, value is the name after transferred.
        :param original_roi_list: tuple of sitk rois from the base image.
        :param tfm: the tfm that contains information for transferring rois
        :param reference_image: the reference (base) image
        :param patient_dict_container: container of the transfer image set.

        """
        for roi_name, new_roi_name in transfer_dict.items():
            for index, name in enumerate(original_roi_list[1]):
                if name == roi_name:
                    sitk_image = original_roi_list[0][index]
                    new_contour = apply_linear_transform(
                        input_image=sitk_image,
                        transform=tfm,
                        reference_image=reference_image,
                        is_structure=True)
                    contour = sitk.GetArrayViewFromImage(new_contour)
                    contours = np.transpose(contour.nonzero())
                    self.save_roi_to_patient_dict_container(
                        contours, new_roi_name, patient_dict_container)

    def save_roi_to_patient_dict_container(self, contours, roi_name,
                                           patient_dict_container):
        """
        Save the transferred ROI to the corresponding rtss.

        :param contours: np array of coordinates of the ROI to be saved.
        :param roi_name: name of the ROI to be saved
        :param patient_dict_container: container of the transfer image set.

        """
        pixels_coords_dict = {}
        slice_ids_dict = get_dict_slice_to_uid(patient_dict_container)
        total_slices = len(slice_ids_dict)
        for contour in contours:
            curr_slice_id = total_slices - contour[0]
            if curr_slice_id >= total_slices:
                curr_slice_id = 0
            if curr_slice_id not in pixels_coords_dict:
                pixels_coords_dict[curr_slice_id] = [
                    tuple([contour[2], contour[1]])
                ]
            else:
                pixels_coords_dict[curr_slice_id].append(
                    tuple([contour[2], contour[1]]))

        rois_to_save = {}
        for key in pixels_coords_dict.keys():
            coords = pixels_coords_dict[key]
            polygon_list = ROI.calculate_concave_hull_of_points(coords)
            if len(polygon_list) > 0:
                rois_to_save[key] = {
                    'ds': patient_dict_container.dataset[key],
                    'coords': polygon_list
                }
        roi_list = ROI.convert_hull_list_to_contours_data(
            rois_to_save, patient_dict_container)

        if len(roi_list) > 0:
            print("Saving ", roi_name)
            if isinstance(patient_dict_container, MovingDictContainer):
                new_rtss = ROI.create_roi(
                    patient_dict_container.get("dataset_rtss"),
                    roi_name,
                    roi_list,
                    rtss_owner="MOVING")
                self.moving_dict_container.set("dataset_rtss", new_rtss)
                self.moving_dict_container.set("rtss_modified", True)
            else:
                new_rtss = ROI.create_roi(
                    patient_dict_container.get("dataset_rtss"), roi_name,
                    roi_list)
                self.patient_dict_container.set("dataset_rtss", new_rtss)
                self.patient_dict_container.set("rtss_modified", True)

    def closeWindow(self):
        """
        function to close transfer roi window
        """
        self.close()
Exemple #10
0
    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()
Exemple #11
0
 def update_image_fusion_ui(self):
     mvd = MovingDictContainer()
     if not mvd.is_empty():
         read_images_for_fusion()
         self.create_image_fusion_tab()
    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_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", [])
Exemple #14
0
    def __init__(self, moving=False):
        QtWidgets.QWidget.__init__(self)
        self.patient_dict_container = PatientDictContainer()
        self.moving_dict_container = MovingDictContainer()
        self.rois = self.patient_dict_container.get("rois")
        self.color_dict = self.init_color_roi(self.patient_dict_container)
        self.patient_dict_container.set("roi_color_dict", self.color_dict)
        self.structure_tab_layout = QtWidgets.QVBoxLayout()

        self.roi_delete_handler = ROIDelOption(
            self.fixed_container_structure_modified)
        self.roi_draw_handler = ROIDrawOption(
            self.fixed_container_structure_modified)
        self.roi_manipulate_handler = ROIManipulateOption(
            self.fixed_container_structure_modified)

        # Create scrolling area widget to contain the content.
        self.scroll_area = QtWidgets.QScrollArea()
        self.scroll_area.setWidgetResizable(True)

        self.scroll_area_content = QtWidgets.QWidget(self.scroll_area)
        self.scroll_area.ensureWidgetVisible(self.scroll_area_content)

        # Create layout for checkboxes and colour squares
        self.layout_content = QtWidgets.QVBoxLayout(self.scroll_area_content)
        self.layout_content.setContentsMargins(0, 0, 0, 0)
        self.layout_content.setSpacing(0)
        self.layout_content.setAlignment(QtCore.Qt.AlignTop
                                         | QtCore.Qt.AlignTop)

        # Create list of standard organ and volume names
        self.standard_organ_names = []
        self.standard_volume_names = []
        self.init_standard_names()

        # Create StructureWidget objects
        self.update_content()

        # Create a modified indicator
        self.modified_indicator_widget = QtWidgets.QWidget()
        self.modified_indicator_widget.setContentsMargins(8, 5, 8, 5)
        modified_indicator_layout = QtWidgets.QHBoxLayout()
        modified_indicator_layout.setAlignment(QtCore.Qt.AlignLeft
                                               | QtCore.Qt.AlignLeft)

        modified_indicator_icon = QtWidgets.QLabel()
        modified_indicator_icon.setPixmap(
            QtGui.QPixmap(
                resource_path("res/images/btn-icons/alert_icon.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)

        self.modified_indicator_widget.setLayout(modified_indicator_layout)
        self.modified_indicator_widget.mouseReleaseEvent = self.\
            save_new_rtss_to_fixed_image_set
        self.modified_indicator_widget.setVisible(False)

        # Create ROI manipulation buttons
        self.button_roi_manipulate = QtWidgets.QPushButton()
        self.button_roi_draw = QtWidgets.QPushButton()
        self.button_roi_delete = QtWidgets.QPushButton()
        self.roi_buttons = QtWidgets.QWidget()
        self.init_roi_buttons()

        # Set layout
        self.structure_tab_layout.addWidget(self.scroll_area)
        self.structure_tab_layout.addWidget(self.modified_indicator_widget)
        self.structure_tab_layout.addWidget(self.roi_buttons)
        self.setLayout(self.structure_tab_layout)
Exemple #15
0
class StructureTab(QtWidgets.QWidget):
    request_update_structures = QtCore.Signal()

    def __init__(self, moving=False):
        QtWidgets.QWidget.__init__(self)
        self.patient_dict_container = PatientDictContainer()
        self.moving_dict_container = MovingDictContainer()
        self.rois = self.patient_dict_container.get("rois")
        self.color_dict = self.init_color_roi(self.patient_dict_container)
        self.patient_dict_container.set("roi_color_dict", self.color_dict)
        self.structure_tab_layout = QtWidgets.QVBoxLayout()

        self.roi_delete_handler = ROIDelOption(
            self.fixed_container_structure_modified)
        self.roi_draw_handler = ROIDrawOption(
            self.fixed_container_structure_modified)
        self.roi_manipulate_handler = ROIManipulateOption(
            self.fixed_container_structure_modified)

        # Create scrolling area widget to contain the content.
        self.scroll_area = QtWidgets.QScrollArea()
        self.scroll_area.setWidgetResizable(True)

        self.scroll_area_content = QtWidgets.QWidget(self.scroll_area)
        self.scroll_area.ensureWidgetVisible(self.scroll_area_content)

        # Create layout for checkboxes and colour squares
        self.layout_content = QtWidgets.QVBoxLayout(self.scroll_area_content)
        self.layout_content.setContentsMargins(0, 0, 0, 0)
        self.layout_content.setSpacing(0)
        self.layout_content.setAlignment(QtCore.Qt.AlignTop
                                         | QtCore.Qt.AlignTop)

        # Create list of standard organ and volume names
        self.standard_organ_names = []
        self.standard_volume_names = []
        self.init_standard_names()

        # Create StructureWidget objects
        self.update_content()

        # Create a modified indicator
        self.modified_indicator_widget = QtWidgets.QWidget()
        self.modified_indicator_widget.setContentsMargins(8, 5, 8, 5)
        modified_indicator_layout = QtWidgets.QHBoxLayout()
        modified_indicator_layout.setAlignment(QtCore.Qt.AlignLeft
                                               | QtCore.Qt.AlignLeft)

        modified_indicator_icon = QtWidgets.QLabel()
        modified_indicator_icon.setPixmap(
            QtGui.QPixmap(
                resource_path("res/images/btn-icons/alert_icon.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)

        self.modified_indicator_widget.setLayout(modified_indicator_layout)
        self.modified_indicator_widget.mouseReleaseEvent = self.\
            save_new_rtss_to_fixed_image_set
        self.modified_indicator_widget.setVisible(False)

        # Create ROI manipulation buttons
        self.button_roi_manipulate = QtWidgets.QPushButton()
        self.button_roi_draw = QtWidgets.QPushButton()
        self.button_roi_delete = QtWidgets.QPushButton()
        self.roi_buttons = QtWidgets.QWidget()
        self.init_roi_buttons()

        # Set layout
        self.structure_tab_layout.addWidget(self.scroll_area)
        self.structure_tab_layout.addWidget(self.modified_indicator_widget)
        self.structure_tab_layout.addWidget(self.roi_buttons)
        self.setLayout(self.structure_tab_layout)

    def init_color_roi(self, dict_container):
        """
        Create a dictionary containing the colors for each structure.
        :param: either PatientDictContainer or MovingDictContainer
        :return: Dictionary where the key is the ROI number and the value a
        QColor object.
        """
        roi_color = dict()
        roi_contour_info = dict_container.get(
            "dict_dicom_tree_rtss")['ROI Contour Sequence']

        if len(roi_contour_info) > 0:
            for item, roi_dict in roi_contour_info.items():
                # Note: the keys of roiContourInfo are "item 0", "item 1",
                # etc. As all the ROI structures are identified by the ROI
                # numbers in the whole code, we get the ROI number 'roi_id'
                # by using the member 'list_roi_numbers'
                id = item.split()[1]
                roi_id = dict_container.get("list_roi_numbers")[int(id)]
                if 'ROI Display Color' in roi_contour_info[item]:
                    RGB_list = roi_contour_info[item]['ROI Display Color'][0]
                    red = RGB_list[0]
                    green = RGB_list[1]
                    blue = RGB_list[2]
                else:
                    seed(1)
                    red = randint(0, 255)
                    green = randint(0, 255)
                    blue = randint(0, 255)

                roi_color[roi_id] = QtGui.QColor(red, green, blue)

        return roi_color

    def init_standard_names(self):
        """
        Create two lists containing standard organ and standard volume names
        as set by the Add-On options.
        """
        with open(data_path('organName.csv'), 'r') as f:
            self.standard_organ_names = []

            csv_input = csv.reader(f)
            header = next(f)  # Ignore the "header" of the column
            for row in csv_input:
                self.standard_organ_names.append(row[0])

        with open(data_path('volumeName.csv'), 'r') as f:
            self.standard_volume_names = []

            csv_input = csv.reader(f)
            header = next(f)  # Ignore the "header" of the column
            for row in csv_input:
                self.standard_volume_names.append(row[1])

    def init_roi_buttons(self):
        icon_roi_delete = QtGui.QIcon()
        icon_roi_delete.addPixmap(
            QtGui.QPixmap(
                resource_path('res/images/btn-icons/delete_icon.png')),
            QtGui.QIcon.Normal, QtGui.QIcon.On)

        icon_roi_draw = QtGui.QIcon()
        icon_roi_draw.addPixmap(
            QtGui.QPixmap(resource_path('res/images/btn-icons/draw_icon.png')),
            QtGui.QIcon.Normal, QtGui.QIcon.On)

        icon_roi_manipulate = QtGui.QIcon()
        icon_roi_manipulate.addPixmap(
            QtGui.QPixmap(
                resource_path('res/images/btn-icons/manipulate_icon.png')),
            QtGui.QIcon.Normal, QtGui.QIcon.On)

        self.button_roi_delete.setIcon(icon_roi_delete)
        self.button_roi_delete.setText("Delete ROI")
        self.button_roi_delete.clicked.connect(self.roi_delete_clicked)

        self.button_roi_draw.setIcon(icon_roi_draw)
        self.button_roi_draw.setText("Draw ROI")
        self.button_roi_draw.clicked.connect(self.roi_draw_clicked)

        self.button_roi_manipulate.setIcon(icon_roi_manipulate)
        self.button_roi_manipulate.setText("Manipulate ROI")
        self.button_roi_manipulate.clicked.connect(self.roi_manipulate_clicked)

        layout_roi_buttons = QtWidgets.QVBoxLayout(self.roi_buttons)
        layout_roi_buttons.setContentsMargins(0, 0, 0, 0)
        layout_roi_buttons.addWidget(self.button_roi_draw)
        layout_roi_buttons.addWidget(self.button_roi_manipulate)
        layout_roi_buttons.addWidget(self.button_roi_delete)

    def update_ui(self, moving=False):
        """
        Update the UI of Structure Tab when a new patient is opened
        """
        self.patient_dict_container = PatientDictContainer()
        self.rois = self.patient_dict_container.get("rois")
        self.color_dict = self.init_color_roi(self.patient_dict_container)
        self.patient_dict_container.set("roi_color_dict", self.color_dict)
        if hasattr(self, "modified_indicator_widget"):
            self.modified_indicator_widget.setParent(None)
        self.update_content()

    def update_content(self):
        """
        Add the contents (color square and checkbox) in the scrolling area
        widget.
        """
        # Clear the children
        for i in reversed(range(self.layout_content.count())):
            self.layout_content.itemAt(i).widget().setParent(None)

        row = 0
        for roi_id, roi_dict in self.rois.items():
            # Creates a widget representing each ROI
            color = self.color_dict[roi_id]
            color.setAlpha(255)
            structure = StructureWidget(roi_id, color, roi_dict['name'], self)
            if roi_id in self.patient_dict_container.get("selected_rois"):
                structure.checkbox.setChecked(Qt.Checked)
            structure.structure_renamed.\
                connect(self.fixed_container_structure_modified)
            self.layout_content.addWidget(structure)
            row += 1

        self.scroll_area.setStyleSheet(
            "QScrollArea {background-color: #ffffff; border-style: none;}")
        self.scroll_area_content.setStyleSheet(
            "QWidget {background-color: #ffffff; border-style: none;}")

        self.scroll_area.setWidget(self.scroll_area_content)

    def roi_delete_clicked(self):
        self.roi_delete_handler.show_roi_delete_options()

    def roi_draw_clicked(self):
        self.roi_draw_handler.show_roi_draw_options()

    def roi_manipulate_clicked(self):
        """ Open ROI Manipulate Window """
        self.roi_manipulate_handler.show_roi_manipulate_options(
            self.color_dict)

    def moving_container_structure_modified(self, changes):
        """
        Executes when a structure of moving container is modified.
        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]

        # 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.moving_dict_container.set("rtss_modified", True)
        self.moving_dict_container.set("dataset_rtss", new_dataset)

        # Refresh ROIs in main page
        self.moving_dict_container.set("rois",
                                       ImageLoading.get_roi_info(new_dataset))
        self.rois = self.moving_dict_container.get("rois")
        contour_data = ImageLoading.get_raw_contour_data(new_dataset)
        self.moving_dict_container.set("raw_contour", contour_data[0])
        self.moving_dict_container.set("num_points", contour_data[1])
        pixluts = ImageLoading.get_pixluts(self.moving_dict_container.dataset)
        self.moving_dict_container.set("pixluts", pixluts)
        self.moving_dict_container.set(
            "list_roi_numbers",
            ordered_list_rois(self.moving_dict_container.get("rois")))
        self.moving_dict_container.set("selected_rois", [])
        self.moving_dict_container.set("dict_polygons_axial", {})
        self.moving_dict_container.set("dict_polygons_sagittal", {})
        self.moving_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.moving_dict_container.set("dict_dicom_tree_rtss",
                                           dicom_tree_rtss.dict)
            self.color_dict = self.init_color_roi(self.moving_dict_container)
            self.moving_dict_container.set("roi_color_dict", self.color_dict)
            if self.moving_dict_container.has_attribute("raw_dvh"):
                # DVH will be outdated once changes to it are made, and
                # recalculation will be required.
                self.moving_dict_container.set("dvh_outdated", True)

        if self.moving_dict_container.has_modality("raw_dvh"):
            # Rename structures in DVH list
            if "rename" in change_description:
                new_raw_dvh = self.moving_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.moving_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 change_description:
                list_of_deleted = []
                new_raw_dvh = self.moving_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.moving_dict_container.set("raw_dvh", new_raw_dvh)

        if "transfer" in change_description \
                and change_description["transfer"] is None:
            self.save_new_rtss_to_moving_image_set()

    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 show_modified_indicator(self):
        self.modified_indicator_widget.setVisible(True)

    def structure_checked(self, state, roi_id):
        """
        Function triggered when the checkbox of a structure is
        checked / unchecked.
        Update the list of selected structures.
        Update the plot of the DVH and the DICOM view.

        :param state: True if the checkbox is checked, False otherwise.
        :param roi_id: ROI number
        """

        selected_rois = self.patient_dict_container.get("selected_rois")
        if state:
            selected_rois.append(roi_id)
        else:
            selected_rois.remove(roi_id)

        self.patient_dict_container.set("selected_rois", selected_rois)
        self.update_dict_polygons(state, roi_id)

        self.request_update_structures.emit()

    def update_dict_polygons(self, state, roi_id):
        """
        Update the polygon dictionaries (axial, coronal, sagittal) used to
        display the ROIs.
        :param state: True if the ROI is selected, False otherwise
        :param roi_id: ROI number
        """
        rois = self.patient_dict_container.get("rois")
        new_dict_polygons_axial = self.patient_dict_container.get(
            "dict_polygons_axial")
        new_dict_polygons_coronal = self.patient_dict_container.get(
            "dict_polygons_coronal")
        new_dict_polygons_sagittal = self.patient_dict_container.get(
            "dict_polygons_sagittal")
        aspect = self.patient_dict_container.get("pixmap_aspect")
        roi_name = rois[roi_id]['name']

        if state:
            new_dict_polygons_axial[roi_name] = {}
            new_dict_polygons_coronal[roi_name] = {}
            new_dict_polygons_sagittal[roi_name] = {}
            dict_rois_contours_axial = get_roi_contour_pixel(
                self.patient_dict_container.get("raw_contour"), [roi_name],
                self.patient_dict_container.get("pixluts"))
            dict_rois_contours_coronal, dict_rois_contours_sagittal = \
                transform_rois_contours(
                    dict_rois_contours_axial)

            for slice_id in self.patient_dict_container.get(
                    "dict_uid").values():
                polygons = calc_roi_polygon(roi_name, slice_id,
                                            dict_rois_contours_axial)
                new_dict_polygons_axial[roi_name][slice_id] = polygons

            for slice_id in range(
                    0,
                    len(self.patient_dict_container.get("pixmaps_coronal"))):
                polygons_coronal = calc_roi_polygon(
                    roi_name, slice_id, dict_rois_contours_coronal,
                    aspect["coronal"])
                polygons_sagittal = calc_roi_polygon(
                    roi_name, slice_id, dict_rois_contours_sagittal,
                    1 / aspect["sagittal"])
                new_dict_polygons_coronal[roi_name][
                    slice_id] = polygons_coronal
                new_dict_polygons_sagittal[roi_name][
                    slice_id] = polygons_sagittal

            self.patient_dict_container.set("dict_polygons_axial",
                                            new_dict_polygons_axial)
            self.patient_dict_container.set("dict_polygons_coronal",
                                            new_dict_polygons_coronal)
            self.patient_dict_container.set("dict_polygons_sagittal",
                                            new_dict_polygons_sagittal)
        else:
            new_dict_polygons_axial.pop(roi_name, None)
            new_dict_polygons_coronal.pop(roi_name, None)
            new_dict_polygons_sagittal.pop(roi_name, None)

    def on_rtss_selected(self, selected_rtss):
        """
        Function to run after a rtss is selected from SelectRTSSPopUp
        """
        self.patient_dict_container.get("existing_rtss_files").clear()
        self.patient_dict_container.get("existing_rtss_files").append(
            selected_rtss)
        self.save_new_rtss(auto=True)

    def display_select_rtss_window(self):
        """
        Display a pop up window that contains all RTSSs attached to the
        selected image set.
        """
        self.select_rtss_window = SelectRTSSPopUp(
            self.patient_dict_container.get("existing_rtss_files"),
            parent=self)
        self.select_rtss_window.signal_rtss_selected.connect(
            self.on_rtss_selected)
        self.select_rtss_window.show()

    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 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 display_confirm_merge(self, duplicated_names):
        confirm_merge = QtWidgets.QMessageBox(parent=self)
        confirm_merge.setIcon(QtWidgets.QMessageBox.Question)
        confirm_merge.setWindowTitle("Merge RTSTRUCTs?")
        confirm_merge.setText("Conflicting ROI names found between new ROIs "
                              "and existing ROIs:\n" + str(duplicated_names) +
                              "\nAre you sure you want to merge the RTSTRUCT "
                              "files? The new ROIs will replace the existing "
                              "ROIs. ")
        button_yes = QtWidgets.QPushButton("Yes, I want to merge")
        button_no = QtWidgets.QPushButton("No, I will change the names")
        """ 
        We want the buttons 'No' & 'Yes' to be displayed in that exact 
        order. QMessageBox displays buttons in respect to their assigned 
        roles. (0 first, then 0 and so on) 'AcceptRole' is 0 and 
        'RejectRole' is 1 thus by counterintuitively assigning 'No' to 
        'AcceptRole' and 'Yes' to 'RejectRole' the buttons are 
        positioned as desired.
        """
        confirm_merge.addButton(button_no, QtWidgets.QMessageBox.AcceptRole)
        confirm_merge.addButton(button_yes, QtWidgets.QMessageBox.RejectRole)
        confirm_merge.exec_()

        if confirm_merge.clickedButton() == button_yes:
            return True
        return False
Exemple #16
0
def write_transform_to_dcm(affine_matrix):
    """
    Function to write data of the top(moving) image respective
    to the base(fixed) image.
    
    This function will require future refactoring when the transform 
    DICOM file becomes in use, as this function only assumes there is 
    one RIGID matrix to be saved.
    
    Args:
        affine_matrix (Any) : array of N dimensions
    """
    patient_dict_container = PatientDictContainer()
    patient_path = patient_dict_container.path

    now = datetime.datetime.now()
    dicom_date = now.strftime("%Y%m%d")
    dicom_time = now.strftime("%H%M")

    top_level_tags_to_copy: list = [
        Tag("PatientName"),
        Tag("PatientID"),
        Tag("StudyDate"),
        Tag("StudyTime"),
        Tag("AccessionNumber"),
        Tag("ReferringPhysicianName"),
        Tag("StudyDescription"),
        Tag("StudyInstanceUID"),
        Tag("StudyID"),
        Tag("PositionReferenceIndicator"),
    ]

    # Get Patient Dataset
    patient_dataset = patient_dict_container.dataset[0]

    # Create a new Dataset
    spatial_registration = pydicom.dataset.Dataset()

    # Copy the Dataset to the new dataset
    for tag in top_level_tags_to_copy:
        # print("Tag ", tag)
        if tag in patient_dataset:
            # print("value of tag in image: ", patient_dataset[tag])
            spatial_registration[tag] = deepcopy(patient_dataset[tag])

    # Get the MovingDictContainer
    moving_dict_container = MovingDictContainer()

    # Conversion of the numpy.ndarray to array of strings
    x = []
    for item in affine_matrix:
        for nested_item in item:
            x.append(str(nested_item))

    # Create a Sequence Item
    matrix_sequence = []
    matrix_sequence_item = pydicom.dataset.Dataset()
    matrix_sequence_item.FrameOfReferenceTransformationMatrixType = 'RIGID'
    matrix_sequence_item.FrameOfReferenceTransformationMatrix = x
    matrix_sequence.append(matrix_sequence_item)

    # Create a Registration Type Code Sequence
    registration_type_code_sequence = []
    reg_type_code_sequence_item = pydicom.dataset.Dataset()
    reg_type_code_sequence_item.CodeValue = "125024"
    reg_type_code_sequence_item.CodingSchemeDesignator = "DCM"
    reg_type_code_sequence_item.CodeMeaning = "Image Content-based Alignment"
    registration_type_code_sequence.append(reg_type_code_sequence_item)

    # Create a Matrix Registration Sequence and add Matrix_Sequence Item
    matrix_registration_sequence = pydicom.dataset.Dataset()
    matrix_registration_sequence.MatrixSequence = matrix_sequence
    matrix_registration_sequence.RegistrationTypeCodeSequence = \
        registration_type_code_sequence

    # Create Reference Image Sequence items
    reference_image_sequences = []
    for x in range(len(moving_dict_container.dataset.keys())):

        try:
            reference_image_sequence_item = pydicom.dataset.Dataset()
            reference_image_sequence_item.ReferencedSOPClassUID = \
                moving_dict_container.dataset[x].SOPClassUID
            reference_image_sequence_item.ReferencedSOPInstanceUID = \
                moving_dict_container.dataset[x].SOPInstanceUID
            reference_image_sequences.append(reference_image_sequence_item)
        except:
            continue

    registration_sequence = pydicom.dataset.Dataset()
    registration_sequence.ReferencedImageSequence = reference_image_sequences
    registration_sequence.FrameOfReferenceUID = \
        patient_dataset.get("FrameOfReferenceUID")
    registration_sequence.MatrixRegistrationSequence = \
        [matrix_registration_sequence]

    # General Information
    spatial_registration.SeriesDate = dicom_date
    spatial_registration.SeriesTime = dicom_time
    spatial_registration.ContentDate = dicom_date
    spatial_registration.ContentTime = dicom_time
    spatial_registration.InstanceCreationDate = dicom_date
    spatial_registration.InstanceCreationTime = dicom_time
    spatial_registration.Modality = "REG"
    spatial_registration.SeriesDescription = "Image Registration"
    spatial_registration.RegistrationSequence = [registration_sequence]
    spatial_registration.SOPClassUID = "1.2.840.10008.5.1.4.1.1.66.1"
    spatial_registration.SOPInstanceUID = \
        patient_dataset.get("SOPInstanceUID")
    spatial_registration.Manufacturer = "OnkoDICOM"
    spatial_registration.StationName = patient_dataset.get("StationName")

    spatial_registration.is_little_endian = True
    spatial_registration.is_implicit_VR = True

    # Place path into Patient->Study folder
    filepath = os.path.join(patient_dict_container.path, 'transform.dcm')

    spatial_registration.save_as(filepath)
Exemple #17
0
def create_moving_model():
    """
    This function initializes all the attributes in the
    MovingDictContainer model required for the operation of the main
    window. This should be called before the
    main window's components are constructed, but after the initial
    values of the MovingDictContainer instance are set (i.e. dataset
    and filepaths).
    """
    ##############################
    #  LOAD PATIENT INFORMATION  #
    ##############################
    moving_dict_container = MovingDictContainer()

    dataset = moving_dict_container.dataset
    filepaths = moving_dict_container.filepaths
    moving_dict_container.set("rtss_modified_moving", False)

    # Determine if dataset is CT for aditional rescaling
    is_ct = False
    if dataset[0].Modality == "CT":
        is_ct = True

    if 'WindowWidth' in dataset[0]:
        if isinstance(dataset[0].WindowWidth, pydicom.valuerep.DSfloat):
            window = int(dataset[0].WindowWidth)
        elif isinstance(dataset[0].WindowWidth, pydicom.multival.MultiValue):
            window = int(dataset[0].WindowWidth[1])
    else:
        window = int(400)

    if 'WindowCenter' in dataset[0]:
        if isinstance(dataset[0].WindowCenter, pydicom.valuerep.DSfloat):
            level = int(dataset[0].WindowCenter) - window / 2
        elif isinstance(dataset[0].WindowCenter, pydicom.multival.MultiValue):
            level = int(dataset[0].WindowCenter[1]) - window / 2
        if is_ct:
            level += CT_RESCALE_INTERCEPT
    else:
        level = int(800)

    moving_dict_container.set("window", window)
    moving_dict_container.set("level", level)

    # Check to see if the imageWindowing.csv file exists
    if os.path.exists(data_path('imageWindowing.csv')):
        # If it exists, read data from file into the self.dict_windowing
        # variable
        dict_windowing = {}
        with open(data_path('imageWindowing.csv'), "r") \
                as fileInput:
            next(fileInput)
            dict_windowing["Normal"] = [window, level]
            for row in fileInput:
                # Format: Organ - Scan - Window - Level
                items = [item for item in row.split(',')]
                dict_windowing[items[0]] = [int(items[2]), int(items[3])]
    else:
        # If csv does not exist, initialize dictionary with default
        # values
        dict_windowing = {
            "Normal": [window, level],
            "Lung": [1600, -300],
            "Bone": [1400, 700],
            "Brain": [160, 950],
            "Soft Tissue": [400, 800],
            "Head and Neck": [275, 900]
        }

    moving_dict_container.set("dict_windowing_moving", dict_windowing)

    if not moving_dict_container.has_attribute("scaled"):
        moving_dict_container.set("scaled", True)
        pixel_values = convert_raw_data(dataset, False, is_ct)
    else:
        pixel_values = convert_raw_data(dataset, True)

    # Calculate the ratio between x axis and y axis of 3 views
    pixmap_aspect = {}
    pixel_spacing = dataset[0].PixelSpacing
    slice_thickness = dataset[0].SliceThickness
    pixmap_aspect["axial"] = pixel_spacing[1] / pixel_spacing[0]
    pixmap_aspect["sagittal"] = pixel_spacing[1] / slice_thickness
    pixmap_aspect["coronal"] = slice_thickness / pixel_spacing[0]
    pixmaps_axial, pixmaps_coronal, pixmaps_sagittal = \
        get_pixmaps(pixel_values, window, level, pixmap_aspect)

    moving_dict_container.set("pixmaps_axial", pixmaps_axial)
    moving_dict_container.set("pixmaps_coronal", pixmaps_coronal)
    moving_dict_container.set("pixmaps_sagittal", pixmaps_sagittal)
    moving_dict_container.set("pixel_values", pixel_values)
    moving_dict_container.set("pixmap_aspect", pixmap_aspect)

    basic_info = get_basic_info(dataset[0])
    moving_dict_container.set("basic_info", basic_info)

    moving_dict_container.set("dict_uid", dict_instance_uid(dataset))

    # Set RTSS attributes
    if moving_dict_container.has_modality("rtss"):
        moving_dict_container.set("file_rtss", filepaths['rtss'])
        moving_dict_container.set("dataset_rtss", dataset['rtss'])

        dicom_tree_rtss = DicomTree(filepaths['rtss'])
        moving_dict_container.set("dict_dicom_tree_rtss", dicom_tree_rtss.dict)

        moving_dict_container.set(
            "list_roi_numbers",
            ordered_list_rois(moving_dict_container.get("rois")))
        moving_dict_container.set("selected_rois", [])

        moving_dict_container.set("dict_polygons", {})

    # Set RTDOSE attributes
    if moving_dict_container.has_modality("rtdose"):
        dicom_tree_rtdose = DicomTree(filepaths['rtdose'])
        moving_dict_container.set("dict_dicom_tree_rtdose",
                                  dicom_tree_rtdose.dict)

        moving_dict_container.set("dose_pixluts", get_dose_pixluts(dataset))

        moving_dict_container.set("selected_doses", [])
        # This will be overwritten if an RTPLAN is present.
        moving_dict_container.set("rx_dose_in_cgray", 1)

    # Set RTPLAN attributes
    if moving_dict_container.has_modality("rtplan"):
        rx_dose_in_cgray = calculate_rx_dose_in_cgray(dataset["rtplan"])
        moving_dict_container.set("rx_dose_in_cgray", rx_dose_in_cgray)

        dicom_tree_rtplan = DicomTree(filepaths['rtplan'])
        moving_dict_container.set("dict_dicom_tree_rtplan",
                                  dicom_tree_rtplan.dict)
Exemple #18
0
class Windowing(QDialog):
    done_signal = QtCore.Signal()

    def __init__(self, text):
        """
        Initialises the widget
        :param text: the window selected
        """
        super(Windowing, self).__init__()

        self.pt_ct_dict_container = PTCTDictContainer()
        self.moving_dict_container = MovingDictContainer()

        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.setWindowIcon(window_icon)
        self.setStyleSheet(self.stylesheet)

        self.text = text

        self.layout = QVBoxLayout()
        self.buttons = QHBoxLayout()

        self.setWindowTitle("Select Views")

        self.label = QLabel("Select views to apply windowing to:")
        self.normal = QtWidgets.QCheckBox("DICOM View")
        self.pet = QtWidgets.QCheckBox("PET/CT: PET")
        self.ct = QtWidgets.QCheckBox("PET/CT: CT")
        self.fusion = QtWidgets.QCheckBox("Image Fusion")
        self.confirm = QtWidgets.QPushButton("Confirm")
        self.cancel = QtWidgets.QPushButton("Cancel")

        self.confirm.clicked.connect(self.confirmed)
        self.confirm.setProperty("QPushButtonClass", "success-button")
        self.cancel.clicked.connect(self.exit_button)
        self.cancel.setProperty("QPushButtonClass", "fail-button")

        self.buttons.addWidget(self.cancel)
        self.buttons.addWidget(self.confirm)

        self.layout.addWidget(self.label)
        self.layout.addWidget(self.normal)
        if not self.pt_ct_dict_container.is_empty():
            self.layout.addWidget(self.pet)
            self.layout.addWidget(self.ct)
        if not self.moving_dict_container.is_empty():
            self.layout.addWidget(self.fusion)
        self.layout.addLayout(self.buttons)

        self.setLayout(self.layout)

    def confirmed(self):
        """
        Triggers when confirm button is clicked
        """
        send = [
            self.normal.isChecked(),
            self.pet.isChecked(),
            self.ct.isChecked(),
            self.fusion.isChecked()]
        windowing_model(self.text, send)
        self.cleanup()
        self.done_signal.emit()

    def set_window(self, text):
        """
        Sets the windowing option
        """
        self.text = text

    def exit_button(self):
        """
        Triggers on exit
        """
        self.cleanup()

    def cleanup(self):
        """
        Resets window for next use
        """
        self.normal.setChecked(False)
        self.pet.setChecked(False)
        self.ct.setChecked(False)
        self.fusion.setChecked(False)
        self.close()