def setup(self):
        """This is called one time when the module GUI is initialized
        """
        ScriptedLoadableModuleWidget.setup(self)

        # Create objects that can be used anywhere in the module. Example: in most cases there should be just one
        # object of the logic class
        self.logic = CIP_PAARatioLogic()

        #
        # Create all the widgets. Example Area
        mainAreaCollapsibleButton = ctk.ctkCollapsibleButton()
        mainAreaCollapsibleButton.text = "Main parameters"
        self.layout.addWidget(mainAreaCollapsibleButton)
        self.mainAreaLayout = qt.QGridLayout(mainAreaCollapsibleButton)

        self.label = qt.QLabel("Select the volume")
        self.label.setStyleSheet("margin:10px 0 20px 7px")
        self.mainAreaLayout.addWidget(self.label, 0, 0)

        self.volumeSelector = slicer.qMRMLNodeComboBox()
        self.volumeSelector.nodeTypes = ("vtkMRMLScalarVolumeNode", "")
        self.volumeSelector.name = "paa_volumeSelector"
        self.volumeSelector.selectNodeUponCreation = True
        self.volumeSelector.autoFillBackground = True
        self.volumeSelector.addEnabled = True
        self.volumeSelector.noneEnabled = False
        self.volumeSelector.removeEnabled = False
        self.volumeSelector.showHidden = False
        self.volumeSelector.showChildNodeTypes = False
        self.volumeSelector.setMRMLScene(slicer.mrmlScene)
        self.volumeSelector.setStyleSheet(
            "margin:0px 0 0px 0; padding:2px 0 2px 5px")
        self.mainAreaLayout.addWidget(self.volumeSelector, 0, 1)

        self.jumptToTemptativeSliceButton = ctk.ctkPushButton()
        self.jumptToTemptativeSliceButton.name = "jumptToTemptativeSliceButton"
        self.jumptToTemptativeSliceButton.text = "Jump to temptative slice"
        self.jumptToTemptativeSliceButton.toolTip = "Jump to the best estimated slice to place the rulers"
        self.jumptToTemptativeSliceButton.setIcon(
            qt.QIcon("{0}/ruler.png".format(SlicerUtil.CIP_ICON_DIR)))
        self.jumptToTemptativeSliceButton.setIconSize(qt.QSize(20, 20))
        self.jumptToTemptativeSliceButton.setStyleSheet("font-weight: bold;")
        # self.jumptToTemptativeSliceButton.setFixedWidth(140)
        self.mainAreaLayout.addWidget(self.jumptToTemptativeSliceButton, 1, 1)

        ### Structure Selector
        self.structuresGroupbox = qt.QGroupBox("Select the structure")
        self.groupboxLayout = qt.QVBoxLayout()
        self.structuresGroupbox.setLayout(self.groupboxLayout)
        self.mainAreaLayout.addWidget(self.structuresGroupbox, 2, 0)

        self.structuresButtonGroup = qt.QButtonGroup()
        # btn = qt.QRadioButton("None")
        # btn.visible = False
        # self.structuresButtonGroup.addButton(btn)
        # self.groupboxLayout.addWidget(btn)

        btn = qt.QRadioButton("Both")
        btn.name = "paaButton"
        btn.checked = True

        self.structuresButtonGroup.addButton(btn, 0)
        self.groupboxLayout.addWidget(btn)

        btn = qt.QRadioButton("Pulmonary Arterial")
        btn.name = "paRadioButton"
        self.structuresButtonGroup.addButton(btn, 1)
        self.groupboxLayout.addWidget(btn)

        btn = qt.QRadioButton("Aorta")
        btn.name = "aortaRadioButton"
        self.structuresButtonGroup.addButton(btn, 2)
        self.groupboxLayout.addWidget(btn)

        ### Buttons toolbox
        self.buttonsToolboxFrame = qt.QFrame()
        self.buttonsToolboxLayout = qt.QGridLayout()
        self.buttonsToolboxFrame.setLayout(self.buttonsToolboxLayout)
        self.mainAreaLayout.addWidget(self.buttonsToolboxFrame, 2, 1)

        self.placeRulersButton = ctk.ctkPushButton()
        self.placeRulersButton.text = "Place ruler/s"
        self.placeRulersButton.name = "placeRulersButton"
        self.placeRulersButton.toolTip = "Place the ruler/s for the selected structure/s in the current slice"
        self.placeRulersButton.setIcon(
            qt.QIcon("{0}/ruler.png".format(SlicerUtil.CIP_ICON_DIR)))
        self.placeRulersButton.setIconSize(qt.QSize(20, 20))
        self.placeRulersButton.setFixedWidth(105)
        self.placeRulersButton.setStyleSheet("font-weight:bold")
        self.buttonsToolboxLayout.addWidget(self.placeRulersButton, 0, 0)

        self.moveUpButton = ctk.ctkPushButton()
        self.moveUpButton.text = "Move up"
        self.moveUpButton.toolTip = "Move the selected ruler/s one slice up"
        self.moveUpButton.setIcon(
            qt.QIcon("{0}/move_up.png".format(SlicerUtil.CIP_ICON_DIR)))
        self.moveUpButton.setIconSize(qt.QSize(20, 20))
        self.moveUpButton.setFixedWidth(95)
        self.buttonsToolboxLayout.addWidget(self.moveUpButton, 0, 1)

        self.moveDownButton = ctk.ctkPushButton()
        self.moveDownButton.text = "Move down"
        self.moveDownButton.toolTip = "Move the selected ruler/s one slice down"
        self.moveDownButton.setIcon(
            qt.QIcon("{0}/move_down.png".format(SlicerUtil.CIP_ICON_DIR)))
        self.moveDownButton.setIconSize(qt.QSize(20, 20))
        self.moveDownButton.setFixedWidth(95)
        self.buttonsToolboxLayout.addWidget(self.moveDownButton, 0, 2)

        self.removeButton = ctk.ctkPushButton()
        self.removeButton.text = "Remove ALL rulers"
        self.removeButton.toolTip = "Remove all the rulers for this volume"
        self.removeButton.setIcon(
            qt.QIcon("{0}/delete.png".format(SlicerUtil.CIP_ICON_DIR)))
        self.removeButton.setIconSize(qt.QSize(20, 20))
        self.buttonsToolboxLayout.addWidget(self.removeButton, 1, 1, 1, 2, 2)

        ### Textboxes
        self.textboxesFrame = qt.QFrame()
        self.textboxesLayout = qt.QFormLayout()
        self.textboxesFrame.setLayout(self.textboxesLayout)
        self.textboxesFrame.setFixedWidth(190)
        self.mainAreaLayout.addWidget(self.textboxesFrame, 3, 0)

        self.paTextBox = qt.QLineEdit()
        self.paTextBox.setReadOnly(True)
        self.textboxesLayout.addRow("PA (mm):  ", self.paTextBox)

        self.aortaTextBox = qt.QLineEdit()
        self.aortaTextBox.setReadOnly(True)
        self.textboxesLayout.addRow("Aorta (mm):  ", self.aortaTextBox)

        self.ratioTextBox = qt.QLineEdit()
        self.ratioTextBox.name = "ratioTextBox"
        self.ratioTextBox.setReadOnly(True)
        self.textboxesLayout.addRow("Ratio PA/A: ", self.ratioTextBox)

        # Save case data
        self.reportsCollapsibleButton = ctk.ctkCollapsibleButton()
        self.reportsCollapsibleButton.text = "Reporting"
        self.layout.addWidget(self.reportsCollapsibleButton)
        self.reportsLayout = qt.QHBoxLayout(self.reportsCollapsibleButton)

        self.storedColumnNames = [
            "caseId", "paDiameterMm", "aortaDiameterMm", "pa1r", "pa1a",
            "pa1s", "pa2r", "pa2a", "pa2s", "a1r", "a1a", "a1s", "a2r", "a2a",
            "a2s"
        ]
        columns = CaseReportsWidget.getColumnKeysNormalizedDictionary(
            self.storedColumnNames)
        self.reportsWidget = CaseReportsWidget(
            self.moduleName,
            columns,
            parentWidget=self.reportsCollapsibleButton)
        self.reportsWidget.setup()

        # Init state
        self.resetModuleState()

        self.preventSavingState = False
        self.saveStateBeforeEnteringModule()
        self.preventSavingState = True

        self.switchToRedView()

        #####
        # Case navigator
        if SlicerUtil.isSlicerACILLoaded():
            caseNavigatorAreaCollapsibleButton = ctk.ctkCollapsibleButton()
            caseNavigatorAreaCollapsibleButton.text = "Case navigator"
            self.layout.addWidget(caseNavigatorAreaCollapsibleButton, 0x0020)
            # caseNavigatorLayout = qt.QVBoxLayout(caseNavigatorAreaCollapsibleButton)

            # Add a case list navigator
            from ACIL.ui import CaseNavigatorWidget
            self.caseNavigatorWidget = CaseNavigatorWidget(
                self.moduleName, caseNavigatorAreaCollapsibleButton)
            self.caseNavigatorWidget.setup()

        self.layout.addStretch()

        # Connections
        self.observers = []

        self.volumeSelector.connect('currentNodeChanged(vtkMRMLNode*)',
                                    self.onVolumeSelectorChanged)
        self.jumptToTemptativeSliceButton.connect(
            'clicked()', self.onJumpToTemptativeSliceButtonClicked)
        self.placeRulersButton.connect('clicked()', self.onPlaceRulersClicked)
        self.moveUpButton.connect('clicked()', self.onMoveUpRulerClicked)
        self.moveDownButton.connect('clicked()', self.onMoveDownRulerClicked)
        self.removeButton.connect('clicked()', self.onRemoveRulerClicked)

        self.reportsWidget.addObservable(
            self.reportsWidget.EVENT_SAVE_BUTTON_CLICKED, self.onSaveReport)

        # Init state
        self.resetModuleState()

        self.preventSavingState = False
        self.saveStateBeforeEnteringModule()
        self.preventSavingState = True
Exemple #2
0
    def setup(self):
        """Init the widget """
        # self.firstLoad = True
        ScriptedLoadableModuleWidget.setup(self)

        # Create objects that can be used anywhere in the module. Example: in most cases there should be just one
        # object of the logic class
        self._initLogic_()

        ##########
        # Main area
        self.mainAreaCollapsibleButton = ctk.ctkCollapsibleButton()
        self.mainAreaCollapsibleButton.text = "Main area"
        self.layout.addWidget(self.mainAreaCollapsibleButton,
                              SlicerUtil.ALIGNMENT_VERTICAL_TOP)
        self.mainLayout = qt.QGridLayout(self.mainAreaCollapsibleButton)

        # Node selector
        volumeLabel = qt.QLabel("Active volume: ")
        volumeLabel.setStyleSheet("margin-left:5px")
        self.mainLayout.addWidget(volumeLabel, 0, 0)
        self.volumeSelector = slicer.qMRMLNodeComboBox()
        self.volumeSelector.nodeTypes = ("vtkMRMLScalarVolumeNode", "")
        self.volumeSelector.selectNodeUponCreation = True
        self.volumeSelector.autoFillBackground = True
        self.volumeSelector.addEnabled = False
        self.volumeSelector.noneEnabled = False
        self.volumeSelector.removeEnabled = False
        self.volumeSelector.showHidden = False
        self.volumeSelector.showChildNodeTypes = False
        self.volumeSelector.setMRMLScene(slicer.mrmlScene)
        self.volumeSelector.setMinimumWidth(150)
        self.volumeSelector.setStyleSheet("margin: 15px 0")
        # self.volumeSelector.selectNodeUponCreation = False
        self.mainLayout.addWidget(self.volumeSelector, 0, 1, 1, 2)
        self.volumeSelector.connect('currentNodeChanged(vtkMRMLNode*)',
                                    self._onMainVolumeChanged_)

        ### Radio buttons frame
        self.radioButtonsFrame = qt.QFrame()
        self.radioButtonsLayout = qt.QHBoxLayout(self.radioButtonsFrame)
        self.typesFrame = qt.QFrame()
        self.radioButtonsLayout.addWidget(self.typesFrame)
        self.typesLayout = qt.QVBoxLayout(self.typesFrame)

        labelsStyle = "font-weight: bold; margin: 0 0 10px 0px;"
        # Types Radio Buttons
        typesLabel = qt.QLabel("Select type")
        typesLabel.setStyleSheet(labelsStyle)
        self.typesLayout.addWidget(typesLabel)
        self.typesRadioButtonGroup = qt.QButtonGroup()
        for key in self.logic.params.mainTypes.keys():
            rbitem = qt.QRadioButton(self.logic.params.getMainTypeLabel(key))
            self.typesRadioButtonGroup.addButton(rbitem, key)
            self.typesLayout.addWidget(rbitem)
        self.typesRadioButtonGroup.buttons()[0].setChecked(True)

        # Subtypes Radio buttons
        # The content will be loaded dynamically every time the main type is modified
        self.subtypesFrame = qt.QFrame()
        self.radioButtonsLayout.addWidget(self.subtypesFrame)
        self.subtypesLayout = qt.QVBoxLayout(self.subtypesFrame)
        subtypesLabel = qt.QLabel("Select subtype")
        subtypesLabel.setStyleSheet(labelsStyle)
        self.subtypesLayout.addWidget(subtypesLabel)
        self.subtypesLayout.setAlignment(SlicerUtil.ALIGNMENT_VERTICAL_TOP)
        self.subtypesRadioButtonGroup = qt.QButtonGroup()
        # Add all the subtypes (we will filter later in "updateState" function)
        for key in self.logic.params.subtypes.keys():
            # Build the description
            rbitem = qt.QRadioButton(self.logic.params.getSubtypeLabel(key))
            self.subtypesRadioButtonGroup.addButton(rbitem, key)
            self.subtypesLayout.addWidget(rbitem,
                                          SlicerUtil.ALIGNMENT_VERTICAL_TOP)
        self.subtypesLayout.addStretch()

        # Region radio buttons
        self.regionsFrame = qt.QFrame()
        self.radioButtonsLayout.addWidget(self.regionsFrame)
        self.regionsLayout = qt.QVBoxLayout(self.regionsFrame)
        regionsLabel = qt.QLabel("Select region")
        regionsLabel.setStyleSheet(labelsStyle)
        self.regionsLayout.addWidget(regionsLabel)
        self.regionsLayout.setAlignment(SlicerUtil.ALIGNMENT_VERTICAL_TOP)
        self.regionsLayout.setStretch(0, 0)
        self.regionsRadioButtonGroup = qt.QButtonGroup()
        self.regionsFrame = qt.QFrame()
        # Add all the regions
        for key in self.logic.params.regions.keys():
            # Build the description
            rbitem = qt.QRadioButton(self.logic.params.getRegionLabel(key))
            self.regionsRadioButtonGroup.addButton(rbitem, key)
            self.regionsLayout.addWidget(rbitem,
                                         SlicerUtil.ALIGNMENT_VERTICAL_TOP)
        self.regionsLayout.addStretch()
        self.regionsRadioButtonGroup.buttons()[0].setChecked(True)

        # Artifact radio buttons (Add them to the same layout as the type)
        # self.separatorLabel = qt.QLabel("------------")
        # labelsStyle = "margin: 5px 0 5px 0;"
        # self.separatorLabel.setStyleSheet(labelsStyle)
        # self.typesLayout.addWidget(self.separatorLabel)
        # self.artifactsLabel = qt.QLabel("Select artifact")
        # labelsStyle = "font-weight: bold; margin: 15px 0 10px 0;"
        # self.artifactsLabel.setStyleSheet(labelsStyle)
        # self.typesLayout.addWidget(self.artifactsLabel)
        # self.artifactsRadioButtonGroup = qt.QButtonGroup()
        # for artifactId in self.logic.params.artifacts.iterkeys():
        #     rbitem = qt.QRadioButton(self.logic.params.getArtifactLabel(artifactId))
        #     self.artifactsRadioButtonGroup.addButton(rbitem, artifactId)
        #     self.typesLayout.addWidget(rbitem)
        # self.artifactsRadioButtonGroup.buttons()[0].setChecked(True)
        #
        self.typesLayout.setAlignment(SlicerUtil.ALIGNMENT_VERTICAL_TOP)
        self.typesLayout.addStretch()

        # Connections
        self.typesRadioButtonGroup.connect("buttonClicked (QAbstractButton*)",
                                           self.__onTypesRadioButtonClicked__)
        self.subtypesRadioButtonGroup.connect(
            "buttonClicked (QAbstractButton*)",
            self.__onSecondaryRadioButtonClicked__)
        self.regionsRadioButtonGroup.connect(
            "buttonClicked (QAbstractButton*)",
            self.__onSecondaryRadioButtonClicked__)
        # self.artifactsRadioButtonGroup.connect("buttonClicked (QAbstractButton*)", self.__onTypesRadioButtonClicked__)

        self.mainLayout.addWidget(self.radioButtonsFrame, 2, 0, 1, 3,
                                  SlicerUtil.ALIGNMENT_VERTICAL_TOP)

        # Save results button
        self.saveResultsButton = ctk.ctkPushButton()
        self.saveResultsButton.setText("Save results")
        self.saveResultsButton.toolTip = "Save the results labelmap in the specified directory"
        self.saveResultsButton.setIcon(
            qt.QIcon("{0}/Save.png".format(SlicerUtil.CIP_ICON_DIR)))
        self.saveResultsButton.setIconSize(qt.QSize(20, 20))
        self.saveResultsButton.setFixedWidth(135)
        self.mainLayout.addWidget(self.saveResultsButton, 4, 0)
        self.saveResultsButton.connect('clicked()',
                                       self._onSaveResultsButtonClicked_)

        # Save results directory button
        defaultPath = os.path.join(
            SlicerUtil.getSettingsDataFolder(self.moduleName),
            "results")  # Assign a default path for the results
        path = SlicerUtil.settingGetOrSetDefault(self.moduleName,
                                                 "SaveResultsDirectory",
                                                 defaultPath)
        self.saveResultsDirectoryButton = ctk.ctkDirectoryButton()
        self.saveResultsDirectoryButton.directory = path
        self.saveResultsDirectoryButton.setMaximumWidth(440)
        self.mainLayout.addWidget(self.saveResultsDirectoryButton, 4, 1, 1, 2)
        self.saveResultsDirectoryButton.connect(
            "directoryChanged (QString)", self._onSaveResultsDirectoryChanged_)

        self._createSegmentEditorWidget_()

        # MIP viewer (by default it will be hidden)
        self.mipCollapsibleButton = ctk.ctkCollapsibleButton()
        self.mipCollapsibleButton.text = "MIP viewer"
        mipLayout = qt.QVBoxLayout(self.mipCollapsibleButton)
        self.mainLayout.addWidget(self.mipCollapsibleButton)
        self.mipViewer = CIPUI.MIPViewerWidget(mipLayout)
        self.mipCollapsibleButton.setVisible(False)
        self.mipViewer.setup()
        self.mipViewer.isCrosshairEnabled = False
        self.mipCollapsibleButton.collapsed = True

        #####
        # Case navigator
        self.caseNavigatorWidget = None
        if SlicerUtil.isSlicerACILLoaded():
            caseNavigatorAreaCollapsibleButton = ctk.ctkCollapsibleButton()
            caseNavigatorAreaCollapsibleButton.text = "Case navigator"
            self.layout.addWidget(caseNavigatorAreaCollapsibleButton, 0x0020)
            # caseNavigatorLayout = qt.QVBoxLayout(caseNavigatorAreaCollapsibleButton)

            # Add a case list navigator
            from ACIL.ui import CaseNavigatorWidget
            self.caseNavigatorWidget = CaseNavigatorWidget(
                self.moduleName, caseNavigatorAreaCollapsibleButton)
            self.caseNavigatorWidget.setup()
            # Listen for event in order to save the current labelmap before moving to the next case
            self.caseNavigatorWidget.addObservable(
                self.caseNavigatorWidget.EVENT_PRE_NEXT,
                self._checkSaveChanges_)
            self.caseNavigatorWidget.addObservable(
                self.caseNavigatorWidget.EVENT_PRE_PREVIOUS,
                self._checkSaveChanges_)
            self.caseNavigatorWidget.addObservable(
                self.caseNavigatorWidget.EVENT_PRE_LABELMAP_LOAD,
                self._onPreNavigatorLabelmapLoaded_)
            self.caseNavigatorWidget.addObservable(
                self.caseNavigatorWidget.EVENT_LABELMAP_LOADED,
                self._onNavigatorLabelmapLoaded_)

        self.layout.addStretch()

        # Extra Connections
        self._createSceneObservers_()

        self.disableEvents = False
        self.setMainTypeGUIProperties()
class CIP_PAARatioWidget(ScriptedLoadableModuleWidget):
    """Uses ScriptedLoadableModuleWidget base class, available at:
    https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
    """
    @property
    def currentVolumeId(self):
        return self.volumeSelector.currentNodeID

    def __init__(self, parent):
        ScriptedLoadableModuleWidget.__init__(self, parent)
        self.moduleName = "CIP_PAARatio"
        from functools import partial

        def __onNodeAddedObserver__(self, caller, eventId, callData):
            """Node added to the Slicer scene"""
            if callData.GetClassName() == 'vtkMRMLScalarVolumeNode' \
                    and slicer.util.mainWindow().moduleSelector().selectedModule == self.moduleName:    # Current module visible
                self.volumeSelector.setCurrentNode(callData)
                SlicerUtil.changeContrastWindow(350, 40)

        self.__onNodeAddedObserver__ = partial(__onNodeAddedObserver__, self)
        self.__onNodeAddedObserver__.CallDataType = vtk.VTK_OBJECT

    def setup(self):
        """This is called one time when the module GUI is initialized
        """
        ScriptedLoadableModuleWidget.setup(self)

        # Create objects that can be used anywhere in the module. Example: in most cases there should be just one
        # object of the logic class
        self.logic = CIP_PAARatioLogic()

        #
        # Create all the widgets. Example Area
        mainAreaCollapsibleButton = ctk.ctkCollapsibleButton()
        mainAreaCollapsibleButton.text = "Main parameters"
        self.layout.addWidget(mainAreaCollapsibleButton)
        self.mainAreaLayout = qt.QGridLayout(mainAreaCollapsibleButton)

        self.label = qt.QLabel("Select the volume")
        self.label.setStyleSheet("margin:10px 0 20px 7px")
        self.mainAreaLayout.addWidget(self.label, 0, 0)

        self.volumeSelector = slicer.qMRMLNodeComboBox()
        self.volumeSelector.nodeTypes = ("vtkMRMLScalarVolumeNode", "")
        self.volumeSelector.name = "paa_volumeSelector"
        self.volumeSelector.selectNodeUponCreation = True
        self.volumeSelector.autoFillBackground = True
        self.volumeSelector.addEnabled = True
        self.volumeSelector.noneEnabled = False
        self.volumeSelector.removeEnabled = False
        self.volumeSelector.showHidden = False
        self.volumeSelector.showChildNodeTypes = False
        self.volumeSelector.setMRMLScene(slicer.mrmlScene)
        self.volumeSelector.setStyleSheet(
            "margin:0px 0 0px 0; padding:2px 0 2px 5px")
        self.mainAreaLayout.addWidget(self.volumeSelector, 0, 1)

        self.jumptToTemptativeSliceButton = ctk.ctkPushButton()
        self.jumptToTemptativeSliceButton.name = "jumptToTemptativeSliceButton"
        self.jumptToTemptativeSliceButton.text = "Jump to temptative slice"
        self.jumptToTemptativeSliceButton.toolTip = "Jump to the best estimated slice to place the rulers"
        self.jumptToTemptativeSliceButton.setIcon(
            qt.QIcon("{0}/ruler.png".format(SlicerUtil.CIP_ICON_DIR)))
        self.jumptToTemptativeSliceButton.setIconSize(qt.QSize(20, 20))
        self.jumptToTemptativeSliceButton.setStyleSheet("font-weight: bold;")
        # self.jumptToTemptativeSliceButton.setFixedWidth(140)
        self.mainAreaLayout.addWidget(self.jumptToTemptativeSliceButton, 1, 1)

        ### Structure Selector
        self.structuresGroupbox = qt.QGroupBox("Select the structure")
        self.groupboxLayout = qt.QVBoxLayout()
        self.structuresGroupbox.setLayout(self.groupboxLayout)
        self.mainAreaLayout.addWidget(self.structuresGroupbox, 2, 0)

        self.structuresButtonGroup = qt.QButtonGroup()
        # btn = qt.QRadioButton("None")
        # btn.visible = False
        # self.structuresButtonGroup.addButton(btn)
        # self.groupboxLayout.addWidget(btn)

        btn = qt.QRadioButton("Both")
        btn.name = "paaButton"
        btn.checked = True

        self.structuresButtonGroup.addButton(btn, 0)
        self.groupboxLayout.addWidget(btn)

        btn = qt.QRadioButton("Pulmonary Arterial")
        btn.name = "paRadioButton"
        self.structuresButtonGroup.addButton(btn, 1)
        self.groupboxLayout.addWidget(btn)

        btn = qt.QRadioButton("Aorta")
        btn.name = "aortaRadioButton"
        self.structuresButtonGroup.addButton(btn, 2)
        self.groupboxLayout.addWidget(btn)

        ### Buttons toolbox
        self.buttonsToolboxFrame = qt.QFrame()
        self.buttonsToolboxLayout = qt.QGridLayout()
        self.buttonsToolboxFrame.setLayout(self.buttonsToolboxLayout)
        self.mainAreaLayout.addWidget(self.buttonsToolboxFrame, 2, 1)

        self.placeRulersButton = ctk.ctkPushButton()
        self.placeRulersButton.text = "Place ruler/s"
        self.placeRulersButton.name = "placeRulersButton"
        self.placeRulersButton.toolTip = "Place the ruler/s for the selected structure/s in the current slice"
        self.placeRulersButton.setIcon(
            qt.QIcon("{0}/ruler.png".format(SlicerUtil.CIP_ICON_DIR)))
        self.placeRulersButton.setIconSize(qt.QSize(20, 20))
        self.placeRulersButton.setFixedWidth(105)
        self.placeRulersButton.setStyleSheet("font-weight:bold")
        self.buttonsToolboxLayout.addWidget(self.placeRulersButton, 0, 0)

        self.moveUpButton = ctk.ctkPushButton()
        self.moveUpButton.text = "Move up"
        self.moveUpButton.toolTip = "Move the selected ruler/s one slice up"
        self.moveUpButton.setIcon(
            qt.QIcon("{0}/move_up.png".format(SlicerUtil.CIP_ICON_DIR)))
        self.moveUpButton.setIconSize(qt.QSize(20, 20))
        self.moveUpButton.setFixedWidth(95)
        self.buttonsToolboxLayout.addWidget(self.moveUpButton, 0, 1)

        self.moveDownButton = ctk.ctkPushButton()
        self.moveDownButton.text = "Move down"
        self.moveDownButton.toolTip = "Move the selected ruler/s one slice down"
        self.moveDownButton.setIcon(
            qt.QIcon("{0}/move_down.png".format(SlicerUtil.CIP_ICON_DIR)))
        self.moveDownButton.setIconSize(qt.QSize(20, 20))
        self.moveDownButton.setFixedWidth(95)
        self.buttonsToolboxLayout.addWidget(self.moveDownButton, 0, 2)

        self.removeButton = ctk.ctkPushButton()
        self.removeButton.text = "Remove ALL rulers"
        self.removeButton.toolTip = "Remove all the rulers for this volume"
        self.removeButton.setIcon(
            qt.QIcon("{0}/delete.png".format(SlicerUtil.CIP_ICON_DIR)))
        self.removeButton.setIconSize(qt.QSize(20, 20))
        self.buttonsToolboxLayout.addWidget(self.removeButton, 1, 1, 1, 2, 2)

        ### Textboxes
        self.textboxesFrame = qt.QFrame()
        self.textboxesLayout = qt.QFormLayout()
        self.textboxesFrame.setLayout(self.textboxesLayout)
        self.textboxesFrame.setFixedWidth(190)
        self.mainAreaLayout.addWidget(self.textboxesFrame, 3, 0)

        self.paTextBox = qt.QLineEdit()
        self.paTextBox.setReadOnly(True)
        self.textboxesLayout.addRow("PA (mm):  ", self.paTextBox)

        self.aortaTextBox = qt.QLineEdit()
        self.aortaTextBox.setReadOnly(True)
        self.textboxesLayout.addRow("Aorta (mm):  ", self.aortaTextBox)

        self.ratioTextBox = qt.QLineEdit()
        self.ratioTextBox.name = "ratioTextBox"
        self.ratioTextBox.setReadOnly(True)
        self.textboxesLayout.addRow("Ratio PA/A: ", self.ratioTextBox)

        # Save case data
        self.reportsCollapsibleButton = ctk.ctkCollapsibleButton()
        self.reportsCollapsibleButton.text = "Reporting"
        self.layout.addWidget(self.reportsCollapsibleButton)
        self.reportsLayout = qt.QHBoxLayout(self.reportsCollapsibleButton)

        self.storedColumnNames = [
            "caseId", "paDiameterMm", "aortaDiameterMm", "pa1r", "pa1a",
            "pa1s", "pa2r", "pa2a", "pa2s", "a1r", "a1a", "a1s", "a2r", "a2a",
            "a2s"
        ]
        columns = CaseReportsWidget.getColumnKeysNormalizedDictionary(
            self.storedColumnNames)
        self.reportsWidget = CaseReportsWidget(
            self.moduleName,
            columns,
            parentWidget=self.reportsCollapsibleButton)
        self.reportsWidget.setup()

        # Init state
        self.resetModuleState()

        self.preventSavingState = False
        self.saveStateBeforeEnteringModule()
        self.preventSavingState = True

        self.switchToRedView()

        #####
        # Case navigator
        if SlicerUtil.isSlicerACILLoaded():
            caseNavigatorAreaCollapsibleButton = ctk.ctkCollapsibleButton()
            caseNavigatorAreaCollapsibleButton.text = "Case navigator"
            self.layout.addWidget(caseNavigatorAreaCollapsibleButton, 0x0020)
            # caseNavigatorLayout = qt.QVBoxLayout(caseNavigatorAreaCollapsibleButton)

            # Add a case list navigator
            from ACIL.ui import CaseNavigatorWidget
            self.caseNavigatorWidget = CaseNavigatorWidget(
                self.moduleName, caseNavigatorAreaCollapsibleButton)
            self.caseNavigatorWidget.setup()

        self.layout.addStretch()

        # Connections
        self.observers = []

        self.volumeSelector.connect('currentNodeChanged(vtkMRMLNode*)',
                                    self.onVolumeSelectorChanged)
        self.jumptToTemptativeSliceButton.connect(
            'clicked()', self.onJumpToTemptativeSliceButtonClicked)
        self.placeRulersButton.connect('clicked()', self.onPlaceRulersClicked)
        self.moveUpButton.connect('clicked()', self.onMoveUpRulerClicked)
        self.moveDownButton.connect('clicked()', self.onMoveDownRulerClicked)
        self.removeButton.connect('clicked()', self.onRemoveRulerClicked)

        self.reportsWidget.addObservable(
            self.reportsWidget.EVENT_SAVE_BUTTON_CLICKED, self.onSaveReport)

        # Init state
        self.resetModuleState()

        self.preventSavingState = False
        self.saveStateBeforeEnteringModule()
        self.preventSavingState = True

    def enter(self):
        """This is invoked every time that we select this module as the active module in Slicer (not only the first time)"""
        # activeVolumeId = SlicerUtil.getActiveVolumeIdInRedSlice()
        # if activeVolumeId is not None:
        #     self.volumeSelector.setCurrentNodeID(activeVolumeId)
        #     if activeVolumeId not in self.logic.currentVolumesLoaded:
        #         self.placeDefaultRulers(activeVolumeId)
        # Save state
        self.saveStateBeforeEnteringModule()

        # Start listening again to scene events
        self.__addSceneObservables__()

        volumeId = self.volumeSelector.currentNodeID
        if volumeId:
            SlicerUtil.displayBackgroundVolume(volumeId)
            # Show the current rulers (if existing)
            self.logic.rulersVisible(volumeId, visible=True)

        # This module always works in Axial
        SlicerUtil.changeLayoutToAxial()

        self.changeToDefaultContrastLevel()

    def exit(self):
        """This is invoked every time that we switch to another module (not only when Slicer is closed)."""
        # Stop listening to Scene events
        self.__removeSceneObservables()

        # Hide rulers
        if self.currentVolumeId:
            self.logic.rulersVisible(self.currentVolumeId, False)

        # Load previous state
        self.restoreStateBeforeExitingModule()

    def cleanup(self):
        """This is invoked as a destructor of the GUI when the module is no longer going to be used"""
        self.__removeSceneObservables()
        self.reportsWidget.cleanup()
        self.reportsWidget = None

    def saveStateBeforeEnteringModule(self):
        """Save the state of the module regarding labelmap, etc. This state will be saved/loaded when
        exiting/entering the module
        """
        if self.preventSavingState:
            # Avoid that the first time that the module loads, the state is saved twice
            self.preventSavingState = False
            return

        # Save existing layout
        self.savedLayout = None
        if slicer.app.layoutManager() is not None:
            self.savedLayout = slicer.app.layoutManager().layout

        # Get the active volume (it it exists)
        activeVolumeId = SlicerUtil.getFirstActiveVolumeId()
        if activeVolumeId is None:
            # Reset state
            self.resetModuleState()
        else:
            # There is a Volume loaded. Save state
            try:
                self.savedVolumeID = activeVolumeId
                displayNode = SlicerUtil.getNode(
                    activeVolumeId).GetDisplayNode()
                self.savedContrastLevel = (displayNode.GetWindow(),
                                           displayNode.GetLevel())
                # activeLabelmapId = SlicerUtil.getFirstActiveLabelmapId()
                # self.savedLabelmapID = activeLabelmapId
                # if activeLabelmapId is None:
                #     self.savedLabelmapOpacity = None
                # else:
                #     self.savedLabelmapOpacity = SlicerUtil.getLabelmapOpacity()
                #     # Hide any labelmap
                #     SlicerUtil.displayLabelmapVolume(None)
            except:
                Util.print_last_exception()
                # Not action really needed
                pass

    def restoreStateBeforeExitingModule(self):
        """Load the last state of the module when the user exited (labelmap, opacity, contrast window, etc.)
        """
        try:
            if self.savedVolumeID:
                # There is a previously saved valid state.
                SlicerUtil.setActiveVolumeIds(self.savedVolumeID)
                SlicerUtil.changeContrastWindow(self.savedContrastLevel[0],
                                                self.savedContrastLevel[1])
                # if self.savedLabelmapID:
                #     print "Restoring active labelmap: " + self.savedLabelmapID
                #     # There was a valid labelmap. Restore it
                #     SlicerUtil.displayLabelmapVolume(self.savedLabelmapID)
                #     # Restore previous opacity
                #     SlicerUtil.changeLabelmapOpacity(self.savedLabelmapOpacity)
                # else:
                #     # Hide labelmap
                #     print "No labelmap saved. Hide all"
                #     SlicerUtil.displayLabelmapVolume(None)
            # else:
            #     # Hide labelmap
            #     print "No volume saved. Hide labelmap"
            #     SlicerUtil.displayLabelmapVolume(None)

            # Restore layout
            SlicerUtil.changeLayout(self.savedLayout)
        except:
            Util.print_last_exception()
            pass

    def resetModuleState(self):
        """ Reset all the module state variables
        """
        self.savedVolumeID = None  # Active grayscale volume ID
        self.savedLabelmapID = None  # Active labelmap node ID
        self.savedLabelmapOpacity = None  # Labelmap opacity
        self.savedContrastLevel = (
            None, None
        )  # Contrast window/level that the user had when entering the module
        SlicerUtil.changeContrastWindow(350, 40)

    def changeToDefaultContrastLevel(self):
        # Preferred contrast
        SlicerUtil.changeContrastWindow(1000, 200)

    def jumpToTemptativeSlice(self, volumeId):
        """ Jump the red window to a predefined slice based on the size of the volume
        :param volumeId:
        """
        # Get the default coordinates of the ruler
        aorta1, aorta2, pa1, pa2 = self.logic.getDefaultCoords(volumeId)
        # Set the display in the right slice
        self.moveRedWindowToSlice(aorta1[2])

        redSliceNode = slicer.util.getFirstNodeByClassByName(
            "vtkMRMLSliceNode", "Red")

        factor = 0.5
        newFOVx = redSliceNode.GetFieldOfView()[0] * factor
        newFOVy = redSliceNode.GetFieldOfView()[1] * factor
        newFOVz = redSliceNode.GetFieldOfView()[2]
        # Move the camera up to fix the view
        redSliceNode.SetXYZOrigin(0, 50, 0)
        # Update the FOV (zoom in)
        redSliceNode.SetFieldOfView(newFOVx, newFOVy, newFOVz)
        # Refresh the data in the viewer
        redSliceNode.UpdateMatrices()

    def placeDefaultRulers(self, volumeId):
        """ Set the Aorta and PA rulers to a default estimated position and jump to that slice
        :param volumeId:
        """
        if not volumeId:
            return
        # Hide all the actual ruler nodes
        self.logic.hideAllRulers()
        # Remove the current rulers for this volume
        self.logic.removeRulers(volumeId)
        # Create the default rulers
        self.logic.createDefaultRulers(volumeId, self.onRulerUpdated)
        # Activate both structures
        self.structuresButtonGroup.buttons()[0].setChecked(True)
        # Jump to the slice where the rulers are
        self.jumpToTemptativeSlice(volumeId)
        # Place the rulers in the current slice
        self.placeRuler()
        # Add the current volume to the list of loaded volumes
        #self.logic.currentVolumesLoaded.add(volumeId)

        # Modify the zoom of the Red slice
        redSliceNode = slicer.util.getFirstNodeByClassByName(
            "vtkMRMLSliceNode", "Red")
        factor = 0.5
        newFOVx = redSliceNode.GetFieldOfView()[0] * factor
        newFOVy = redSliceNode.GetFieldOfView()[1] * factor
        newFOVz = redSliceNode.GetFieldOfView()[2]
        redSliceNode.SetFieldOfView(newFOVx, newFOVy, newFOVz)
        # Move the camera up to fix the view
        redSliceNode.SetXYZOrigin(0, 50, 0)
        # Refresh the data in the viewer
        redSliceNode.UpdateMatrices()

    def placeRuler(self):
        """ Place one or the two rulers in the current visible slice in Red node
        """
        volumeId = self.volumeSelector.currentNodeID
        if volumeId == '':
            self.showUnselectedVolumeWarningMessage()
            return

        selectedStructure = self.getCurrentSelectedStructure()
        if selectedStructure == self.logic.NONE:
            qt.QMessageBox.warning(
                slicer.util.mainWindow(), 'Review structure',
                'Please select Pulmonary Arterial, Aorta or both to place the right ruler/s'
            )
            return

        # Get the current slice
        currentSlice = self.getCurrentRedWindowSlice()

        if selectedStructure == self.logic.BOTH:
            structures = [self.logic.PA, self.logic.AORTA]
        else:
            structures = [selectedStructure]

        for structure in structures:
            self.logic.placeRulerInSlice(volumeId, structure, currentSlice,
                                         self.onRulerUpdated)

        self.refreshTextboxes()

    def getCurrentSelectedStructure(self):
        """ Get the current selected structure id
        :return: self.logic.AORTA or self.logic.PA
        """
        selectedStructureText = self.structuresButtonGroup.checkedButton().text
        if selectedStructureText == "Aorta": return self.logic.AORTA
        elif selectedStructureText == "Pulmonary Arterial":
            return self.logic.PA
        elif selectedStructureText == "Both":
            return self.logic.BOTH
        return self.logic.NONE

    def stepSlice(self, offset):
        """ Move the selected structure one slice up or down
        :param offset: +1 or -1
        :return:
        """
        volumeId = self.volumeSelector.currentNodeID

        if volumeId == '':
            self.showUnselectedVolumeWarningMessage()
            return

        selectedStructure = self.getCurrentSelectedStructure()
        if selectedStructure == self.logic.NONE:
            self.showUnselectedStructureWarningMessage()
            return

        if selectedStructure == self.logic.BOTH:
            # Move both rulers
            self.logic.stepSlice(volumeId, self.logic.AORTA, offset)
            newSlice = self.logic.stepSlice(volumeId, self.logic.PA, offset)
        else:
            newSlice = self.logic.stepSlice(volumeId, selectedStructure,
                                            offset)

        self.moveRedWindowToSlice(newSlice)

    def removeRulers(self):
        """ Remove all the rulers related to the current volume node
        :return:
        """
        self.logic.removeRulers(self.volumeSelector.currentNodeID)
        self.refreshTextboxes(reset=True)

    def getCurrentRedWindowSlice(self):
        """ Get the current slice (in RAS) of the Red window
        :return:
        """
        redNodeSliceNode = slicer.app.layoutManager().sliceWidget(
            'Red').sliceLogic().GetSliceNode()
        return redNodeSliceNode.GetSliceOffset()

    def moveRedWindowToSlice(self, newSlice):
        """ Moves the red display to the specified RAS slice
        :param newSlice: slice to jump (RAS format)
        :return:
        """
        redNodeSliceNode = slicer.app.layoutManager().sliceWidget(
            'Red').sliceLogic().GetSliceNode()
        redNodeSliceNode.JumpSlice(0, 0, newSlice)

    def refreshTextboxes(self, reset=False):
        """ Update the information of the textboxes that give information about the measurements
        """
        self.aortaTextBox.setText("0")
        self.paTextBox.setText("0")
        self.ratioTextBox.setText("0")
        self.ratioTextBox.setStyleSheet(
            " QLineEdit { background-color: white; color: black}")

        volumeId = self.volumeSelector.currentNodeID
        # if volumeId not in self.logic.currentVolumesLoaded:
        #     return

        if volumeId:
            self.logic.changeActiveRulersColor(volumeId,
                                               self.logic.defaultColor)
        aorta = None
        pa = None
        if not reset:
            rulerAorta, newAorta = self.logic.getRulerNodeForVolumeAndStructure(
                self.volumeSelector.currentNodeID,
                self.logic.AORTA,
                createIfNotExist=False)
            rulerPA, newPA = self.logic.getRulerNodeForVolumeAndStructure(
                self.volumeSelector.currentNodeID,
                self.logic.PA,
                createIfNotExist=False)
            if rulerAorta:
                aorta = rulerAorta.GetDistanceMeasurement()
                self.aortaTextBox.setText(str(aorta))
            if rulerPA:
                pa = rulerPA.GetDistanceMeasurement()
                self.paTextBox.setText(str(pa))
            if pa is not None and aorta is not None and aorta != 0:
                try:
                    ratio = pa / aorta
                    self.ratioTextBox.setText(str(ratio))
                    if ratio > 1.0:
                        # Switch colors ("alarm")
                        st = " QLineEdit {{ background-color: rgb({0}, {1}, {2}); color: white }}". \
                                                        format(int(self.logic.defaultWarningColor[0]*255),
                                                                int(self.logic.defaultWarningColor[1]*255),
                                                                int(self.logic.defaultWarningColor[2]*255))
                        self.ratioTextBox.setStyleSheet(st)
                        self.logic.changeActiveRulersColor(
                            volumeId, self.logic.defaultWarningColor)
                except Exception:
                    Util.print_last_exception()

    def showUnselectedVolumeWarningMessage(self):
        qt.QMessageBox.warning(slicer.util.mainWindow(), 'Select a volume',
                               'Please select a volume')

    def showUnselectedStructureWarningMessage(self):
        qt.QMessageBox.warning(
            slicer.util.mainWindow(), 'Review structure',
            'Please select Aorta, Pulmonary Arterial or Both to place the right ruler/s'
        )

    def switchToRedView(self):
        """ Switch the layout to Red slice only
        :return:
        """
        layoutManager = slicer.app.layoutManager()
        # Test the layout manager is not none in case the module is initialized without a main window
        # This happens for example in automatic tests
        if layoutManager is not None:
            layoutManager.setLayout(6)

    def __addSceneObservables__(self):
        self.observers.append(
            slicer.mrmlScene.AddObserver(slicer.vtkMRMLScene.NodeAddedEvent,
                                         self.__onNodeAddedObserver__))
        self.observers.append(
            slicer.mrmlScene.AddObserver(slicer.vtkMRMLScene.EndCloseEvent,
                                         self.__onSceneClosed__))

    def __removeSceneObservables(self):
        for observer in self.observers:
            slicer.mrmlScene.RemoveObserver(observer)
            self.observers.remove(observer)

    #########
    # EVENTS
    def onVolumeSelectorChanged(self, node):
        #if node is not None and node.GetID() not in self.currentVolumesLoaded:
        # if node is not None:
        #     # New node. Load default rulers
        #     if node.GetID() not in self.logic.currentVolumesLoaded:
        #         self.placeDefaultRulers(node.GetID())
        logging.info("Volume selector node changed: {0}".format(
            '(None)' if node is None else node.GetName()))
        # Preferred contrast (TODO: set right level)
        SlicerUtil.changeContrastWindow(1144, 447)
        self.refreshTextboxes()

    def onStructureClicked(self, button):
        fiducialsNode = self.getFiducialsNode(
            self.volumeSelector.currentNodeID)
        if fiducialsNode is not None:
            self.__addRuler__(button.text, self.volumeSelector.currentNodeID)

            markupsLogic = slicer.modules.markups.logic()
            markupsLogic.SetActiveListID(fiducialsNode)

            applicationLogic = slicer.app.applicationLogic()
            selectionNode = applicationLogic.GetSelectionNode()

            selectionNode.SetReferenceActivePlaceNodeClassName(
                "vtkMRMLAnnotationRulerNode")
            interactionNode = applicationLogic.GetInteractionNode()
            interactionNode.SwitchToSinglePlaceMode()

    def onJumpToTemptativeSliceButtonClicked(self):
        volumeId = self.volumeSelector.currentNodeID
        if volumeId == '':
            self.showUnselectedVolumeWarningMessage()
            return
        #self.placeDefaultRulers(volumeId)
        self.jumpToTemptativeSlice(volumeId)

    def onRulerUpdated(self, node, event):
        self.refreshTextboxes()

    def onPlaceRulersClicked(self):
        self.placeRuler()

    def onMoveUpRulerClicked(self):
        self.stepSlice(1)

    def onMoveDownRulerClicked(self):
        self.stepSlice(-1)

    def onRemoveRulerClicked(self):
        if (qt.QMessageBox.question(
                slicer.util.mainWindow(), 'Remove rulers',
                'Are you sure you want to remove all the rulers from this volume?',
                qt.QMessageBox.Yes | qt.QMessageBox.No)) == qt.QMessageBox.Yes:
            self.logic.removeRulers(self.volumeSelector.currentNodeID)
            self.refreshTextboxes()

    def onSaveReport(self):
        """ Save the current values in a persistent csv file
        :return:
        """
        volumeId = self.volumeSelector.currentNodeID
        if volumeId:
            caseName = slicer.mrmlScene.GetNodeByID(volumeId).GetName()
            coords = [0, 0, 0, 0]
            pa1 = pa2 = a1 = a2 = None
            # PA
            rulerNode, newNode = self.logic.getRulerNodeForVolumeAndStructure(
                volumeId, self.logic.PA, createIfNotExist=False)
            if rulerNode:
                # Get current RAS coords
                rulerNode.GetPositionWorldCoordinates1(coords)
                pa1 = list(coords)
                rulerNode.GetPositionWorldCoordinates2(coords)
                pa2 = list(coords)
            # AORTA
            rulerNode, newNode = self.logic.getRulerNodeForVolumeAndStructure(
                volumeId, self.logic.AORTA, createIfNotExist=False)
            if rulerNode:
                rulerNode.GetPositionWorldCoordinates1(coords)
                a1 = list(coords)
                rulerNode.GetPositionWorldCoordinates2(coords)
                a2 = list(coords)
            self.reportsWidget.insertRow(
                caseId=caseName,
                paDiameterMm=self.paTextBox.text,
                aortaDiameterMm=self.aortaTextBox.text,
                pa1r=pa1[0] if pa1 is not None else '',
                pa1a=pa1[1] if pa1 is not None else '',
                pa1s=pa1[2] if pa1 is not None else '',
                pa2r=pa2[0] if pa2 is not None else '',
                pa2a=pa2[1] if pa2 is not None else '',
                pa2s=pa2[2] if pa2 is not None else '',
                a1r=a1[0] if a1 is not None else '',
                a1a=a1[1] if a1 is not None else '',
                a1s=a1[2] if a1 is not None else '',
                a2r=a2[0] if a2 is not None else '',
                a2a=a2[1] if a2 is not None else '',
                a2s=a2[2] if a2 is not None else '')
            qt.QMessageBox.information(slicer.util.mainWindow(), 'Data saved',
                                       'The data were saved successfully')

    def __onSceneClosed__(self, arg1, arg2):
        """ Scene closed. Reset currently loaded volumes
        :param arg1:
        :param arg2:
        :return:
        """
        #self.logic.currentVolumesLoaded.clear()
        self.logic.currentActiveVolumeId = None
Exemple #4
0
    def setup(self):
        """This is called one time when the module GUI is initialized
        """
        ScriptedLoadableModuleWidget.setup(self)

        # Create objects that can be used anywhere in the module. Example: in most cases there should be just one
        # object of the logic class
        self.logic = CIP_ParenchymaSubtypeTrainingLogic()
        self.currentVolumeLoaded = None
        self.blockNodeEvents = False

        ##########
        # Main area
        self.mainAreaCollapsibleButton = ctk.ctkCollapsibleButton()
        self.mainAreaCollapsibleButton.text = "Main area"
        self.layout.addWidget(self.mainAreaCollapsibleButton,
                              SlicerUtil.ALIGNMENT_VERTICAL_TOP)
        self.mainLayout = qt.QGridLayout(self.mainAreaCollapsibleButton)

        # Node selector
        volumeLabel = qt.QLabel("Active volume: ")
        volumeLabel.setStyleSheet("margin-left:5px")
        self.mainLayout.addWidget(volumeLabel, 0, 0)
        self.volumeSelector = slicer.qMRMLNodeComboBox()
        self.volumeSelector.nodeTypes = ("vtkMRMLScalarVolumeNode", "")
        self.volumeSelector.selectNodeUponCreation = True
        self.volumeSelector.autoFillBackground = True
        self.volumeSelector.addEnabled = False
        self.volumeSelector.noneEnabled = False
        self.volumeSelector.removeEnabled = False
        self.volumeSelector.showHidden = False
        self.volumeSelector.showChildNodeTypes = False
        self.volumeSelector.setMRMLScene(slicer.mrmlScene)
        self.volumeSelector.setFixedWidth(250)
        self.volumeSelector.setStyleSheet("margin: 15px 0")
        #self.volumeSelector.selectNodeUponCreation = False
        self.mainLayout.addWidget(self.volumeSelector, 0, 1, 1, 3)

        labelsStyle = "font-weight: bold; margin: 0 0 5px 5px;"
        # Types Radio Buttons
        typesLabel = qt.QLabel("Select the type")
        typesLabel.setStyleSheet(labelsStyle)
        typesLabel.setFixedHeight(15)
        self.mainLayout.addWidget(typesLabel, 1, 0)
        self.typesFrame = qt.QFrame()
        self.typesLayout = qt.QVBoxLayout(self.typesFrame)
        self.mainLayout.addWidget(self.typesFrame, 2, 0,
                                  SlicerUtil.ALIGNMENT_VERTICAL_TOP)
        self.typesRadioButtonGroup = qt.QButtonGroup()
        for key in self.logic.params.mainTypes.iterkeys():
            rbitem = qt.QRadioButton(self.logic.params.getMainTypeLabel(key))
            self.typesRadioButtonGroup.addButton(rbitem, key)
            self.typesLayout.addWidget(rbitem)
        self.typesRadioButtonGroup.buttons()[0].setChecked(True)

        # Subtypes Radio buttons
        subtypesLabel = qt.QLabel("Select the subtype")
        subtypesLabel.setStyleSheet(labelsStyle)
        subtypesLabel.setFixedHeight(15)
        self.mainLayout.addWidget(subtypesLabel, 1, 1)
        self.subtypesRadioButtonGroup = qt.QButtonGroup()
        self.subtypesFrame = qt.QFrame()
        self.subtypesFrame.setMinimumHeight(275)
        self.subtypesLayout = qt.QVBoxLayout(self.subtypesFrame)
        self.subtypesLayout.setAlignment(SlicerUtil.ALIGNMENT_VERTICAL_TOP)
        self.subtypesLayout.setStretch(0, 0)
        self.mainLayout.addWidget(
            self.subtypesFrame, 2, 1,
            SlicerUtil.ALIGNMENT_VERTICAL_TOP)  # Put the frame in the top
        # The content will be loaded dynamically every time the main type is modified

        # Artifact radio buttons
        self.artifactsLabel = qt.QLabel("Artifact")
        self.artifactsLabel.setStyleSheet(labelsStyle)
        self.artifactsLabel.setFixedHeight(15)
        self.mainLayout.addWidget(self.artifactsLabel, 1, 2)
        #self.mainLayout.addWidget(qt.QLabel("Select the artifact"), 1, 1)
        self.artifactsRadioButtonGroup = qt.QButtonGroup()
        self.artifactsFrame = qt.QFrame()
        self.artifactsLayout = qt.QVBoxLayout(self.artifactsFrame)
        self.mainLayout.addWidget(self.artifactsFrame, 2, 2,
                                  SlicerUtil.ALIGNMENT_VERTICAL_TOP)
        self.artifactsRadioButtonGroup = qt.QButtonGroup()
        for artifactId in self.logic.params.artifacts.iterkeys():
            rbitem = qt.QRadioButton(
                self.logic.params.getArtifactLabel(artifactId))
            self.artifactsRadioButtonGroup.addButton(rbitem, artifactId)
            self.artifactsLayout.addWidget(rbitem)
        self.artifactsRadioButtonGroup.buttons()[0].setChecked(True)

        # Load caselist button
        self.loadButton = ctk.ctkPushButton()
        self.loadButton.text = "Load fiducials file"
        self.loadButton.setIcon(
            qt.QIcon("{0}/open_file.png".format(SlicerUtil.CIP_ICON_DIR)))
        self.loadButton.setIconSize(qt.QSize(20, 20))
        self.loadButton.setFixedWidth(135)
        self.mainLayout.addWidget(self.loadButton, 3, 0)

        # Remove fiducial button
        self.removeLastFiducialButton = ctk.ctkPushButton()
        self.removeLastFiducialButton.text = "Remove last fiducial"
        self.removeLastFiducialButton.toolTip = "Remove the last fiducial added"
        self.removeLastFiducialButton.setIcon(
            qt.QIcon("{0}/delete.png".format(SlicerUtil.CIP_ICON_DIR)))
        self.removeLastFiducialButton.setIconSize(qt.QSize(20, 20))
        self.removeLastFiducialButton.setFixedWidth(200)
        self.mainLayout.addWidget(self.removeLastFiducialButton, 3, 1)

        # Save results button
        self.saveResultsButton = ctk.ctkPushButton()
        self.saveResultsButton.setText("Save markups")
        self.saveResultsButton.toolTip = "Save the markups in the specified directory"
        self.saveResultsButton.setIcon(
            qt.QIcon("{0}/Save.png".format(SlicerUtil.CIP_ICON_DIR)))
        self.saveResultsButton.setIconSize(qt.QSize(20, 20))
        self.mainLayout.addWidget(self.saveResultsButton, 4, 0)

        # Save results directory button
        defaultPath = os.path.join(
            SlicerUtil.getSettingsDataFolder(self.moduleName),
            "results")  # Assign a default path for the results
        path = SlicerUtil.settingGetOrSetDefault(self.moduleName,
                                                 "SaveResultsDirectory",
                                                 defaultPath)
        self.saveResultsDirectoryButton = ctk.ctkDirectoryButton()
        self.saveResultsDirectoryButton.directory = path
        self.saveResultsDirectoryButton.setMaximumWidth(375)
        self.mainLayout.addWidget(self.saveResultsDirectoryButton, 4, 1, 1, 2)

        #####
        # Case navigator
        self.caseNavigatorWidget = None
        if SlicerUtil.isSlicerACILLoaded():
            caseNavigatorAreaCollapsibleButton = ctk.ctkCollapsibleButton()
            caseNavigatorAreaCollapsibleButton.text = "Case navigator"
            self.layout.addWidget(caseNavigatorAreaCollapsibleButton, 0x0020)
            # caseNavigatorLayout = qt.QVBoxLayout(caseNavigatorAreaCollapsibleButton)

            # Add a case list navigator
            from ACIL.ui import CaseNavigatorWidget
            self.caseNavigatorWidget = CaseNavigatorWidget(
                self.moduleName, caseNavigatorAreaCollapsibleButton)
            self.caseNavigatorWidget.setup()
            # Listen for the event of loading a new labelmap
            self.caseNavigatorWidget.addObservable(
                self.caseNavigatorWidget.EVENT_LABELMAP_LOADED,
                self.__onNewILDClassificationLabelmapLoaded__)

        self.layout.addStretch()

        self.updateState()

        # Connections
        self.typesRadioButtonGroup.connect("buttonClicked (QAbstractButton*)",
                                           self.__onTypesRadioButtonClicked__)
        self.subtypesRadioButtonGroup.connect(
            "buttonClicked (QAbstractButton*)",
            self.__onSubtypesRadioButtonClicked__)
        self.artifactsRadioButtonGroup.connect(
            "buttonClicked (QAbstractButton*)",
            self.__onSubtypesRadioButtonClicked__)

        self.volumeSelector.connect('currentNodeChanged(vtkMRMLNode*)',
                                    self.__onCurrentNodeChanged__)
        self.loadButton.connect('clicked()', self.openFiducialsFile)
        self.removeLastFiducialButton.connect(
            'clicked()', self.__onRemoveLastFiducialButtonClicked__)
        # self.saveResultsOpenDirectoryDialogButton.connect('clicked()', self.onOpenDirectoryDialogButtonClicked)
        self.saveResultsDirectoryButton.connect(
            "directoryChanged (QString)",
            self.__onSaveResultsDirectoryChanged__)
        self.saveResultsButton.connect('clicked()',
                                       self.__onSaveResultsButtonClicked__)

        self.observers = []
        self.observers.append(
            slicer.mrmlScene.AddObserver(slicer.vtkMRMLScene.NodeAddedEvent,
                                         self.__onNodeAddedObserver__))
        self.observers.append(
            slicer.mrmlScene.AddObserver(slicer.vtkMRMLScene.EndCloseEvent,
                                         self.__onSceneClosed__))
Exemple #5
0
class CIP_ParenchymaSubtypeTrainingLabellingWidget(ScriptedLoadableModuleWidget
                                                   ):
    """GUI object"""
    def __init__(self, parent):
        ScriptedLoadableModuleWidget.__init__(self, parent)

        # from functools import partial
        # def __onNodeAddedObserver__(self, caller, eventId, callData):
        #     """Node added to the Slicer scene"""
        #     # if callData.GetClassName() == 'vtkMRMLScalarVolumeNode' \
        #     #         and slicer.util.mainWindow().moduleSelector().selectedModule == self.moduleName:
        #     #     self.__onNewVolumeLoaded__(callData)
        #     #if callData.GetClassName() == 'vtkMRMLLabelMapVolumeNode':
        #     self._onNewLabelmapLoaded_(callData)
        #
        #
        # self.__onNodeAddedObserver__ = partial(__onNodeAddedObserver__, self)
        # self.__onNodeAddedObserver__.CallDataType = vtk.VTK_OBJECT

        self.firstLoad = True
        self.activeEditorTools = None
        self.pendingChangesIdsList = []

    @property
    def labelmapNodeNameExtension(self):
        return "parenchymaTrainingLabelMap"

    ################
    # Main methods
    ################
    def setup(self):
        """Init the widget """
        # self.firstLoad = True
        ScriptedLoadableModuleWidget.setup(self)

        # Create objects that can be used anywhere in the module. Example: in most cases there should be just one
        # object of the logic class
        self._initLogic_()

        ##########
        # Main area
        self.mainAreaCollapsibleButton = ctk.ctkCollapsibleButton()
        self.mainAreaCollapsibleButton.text = "Main area"
        self.layout.addWidget(self.mainAreaCollapsibleButton,
                              SlicerUtil.ALIGNMENT_VERTICAL_TOP)
        self.mainLayout = qt.QGridLayout(self.mainAreaCollapsibleButton)

        # Node selector
        volumeLabel = qt.QLabel("Active volume: ")
        volumeLabel.setStyleSheet("margin-left:5px")
        self.mainLayout.addWidget(volumeLabel, 0, 0)
        self.volumeSelector = slicer.qMRMLNodeComboBox()
        self.volumeSelector.nodeTypes = ("vtkMRMLScalarVolumeNode", "")
        self.volumeSelector.selectNodeUponCreation = True
        self.volumeSelector.autoFillBackground = True
        self.volumeSelector.addEnabled = False
        self.volumeSelector.noneEnabled = False
        self.volumeSelector.removeEnabled = False
        self.volumeSelector.showHidden = False
        self.volumeSelector.showChildNodeTypes = False
        self.volumeSelector.setMRMLScene(slicer.mrmlScene)
        self.volumeSelector.setMinimumWidth(150)
        self.volumeSelector.setStyleSheet("margin: 15px 0")
        # self.volumeSelector.selectNodeUponCreation = False
        self.mainLayout.addWidget(self.volumeSelector, 0, 1, 1, 2)
        self.volumeSelector.connect('currentNodeChanged(vtkMRMLNode*)',
                                    self._onMainVolumeChanged_)

        ### Radio buttons frame
        self.radioButtonsFrame = qt.QFrame()
        self.radioButtonsLayout = qt.QHBoxLayout(self.radioButtonsFrame)
        self.typesFrame = qt.QFrame()
        self.radioButtonsLayout.addWidget(self.typesFrame)
        self.typesLayout = qt.QVBoxLayout(self.typesFrame)

        labelsStyle = "font-weight: bold; margin: 0 0 10px 0px;"
        # Types Radio Buttons
        typesLabel = qt.QLabel("Select type")
        typesLabel.setStyleSheet(labelsStyle)
        self.typesLayout.addWidget(typesLabel)
        self.typesRadioButtonGroup = qt.QButtonGroup()
        for key in self.logic.params.mainTypes.keys():
            rbitem = qt.QRadioButton(self.logic.params.getMainTypeLabel(key))
            self.typesRadioButtonGroup.addButton(rbitem, key)
            self.typesLayout.addWidget(rbitem)
        self.typesRadioButtonGroup.buttons()[0].setChecked(True)

        # Subtypes Radio buttons
        # The content will be loaded dynamically every time the main type is modified
        self.subtypesFrame = qt.QFrame()
        self.radioButtonsLayout.addWidget(self.subtypesFrame)
        self.subtypesLayout = qt.QVBoxLayout(self.subtypesFrame)
        subtypesLabel = qt.QLabel("Select subtype")
        subtypesLabel.setStyleSheet(labelsStyle)
        self.subtypesLayout.addWidget(subtypesLabel)
        self.subtypesLayout.setAlignment(SlicerUtil.ALIGNMENT_VERTICAL_TOP)
        self.subtypesRadioButtonGroup = qt.QButtonGroup()
        # Add all the subtypes (we will filter later in "updateState" function)
        for key in self.logic.params.subtypes.keys():
            # Build the description
            rbitem = qt.QRadioButton(self.logic.params.getSubtypeLabel(key))
            self.subtypesRadioButtonGroup.addButton(rbitem, key)
            self.subtypesLayout.addWidget(rbitem,
                                          SlicerUtil.ALIGNMENT_VERTICAL_TOP)
        self.subtypesLayout.addStretch()

        # Region radio buttons
        self.regionsFrame = qt.QFrame()
        self.radioButtonsLayout.addWidget(self.regionsFrame)
        self.regionsLayout = qt.QVBoxLayout(self.regionsFrame)
        regionsLabel = qt.QLabel("Select region")
        regionsLabel.setStyleSheet(labelsStyle)
        self.regionsLayout.addWidget(regionsLabel)
        self.regionsLayout.setAlignment(SlicerUtil.ALIGNMENT_VERTICAL_TOP)
        self.regionsLayout.setStretch(0, 0)
        self.regionsRadioButtonGroup = qt.QButtonGroup()
        self.regionsFrame = qt.QFrame()
        # Add all the regions
        for key in self.logic.params.regions.keys():
            # Build the description
            rbitem = qt.QRadioButton(self.logic.params.getRegionLabel(key))
            self.regionsRadioButtonGroup.addButton(rbitem, key)
            self.regionsLayout.addWidget(rbitem,
                                         SlicerUtil.ALIGNMENT_VERTICAL_TOP)
        self.regionsLayout.addStretch()
        self.regionsRadioButtonGroup.buttons()[0].setChecked(True)

        # Artifact radio buttons (Add them to the same layout as the type)
        # self.separatorLabel = qt.QLabel("------------")
        # labelsStyle = "margin: 5px 0 5px 0;"
        # self.separatorLabel.setStyleSheet(labelsStyle)
        # self.typesLayout.addWidget(self.separatorLabel)
        # self.artifactsLabel = qt.QLabel("Select artifact")
        # labelsStyle = "font-weight: bold; margin: 15px 0 10px 0;"
        # self.artifactsLabel.setStyleSheet(labelsStyle)
        # self.typesLayout.addWidget(self.artifactsLabel)
        # self.artifactsRadioButtonGroup = qt.QButtonGroup()
        # for artifactId in self.logic.params.artifacts.iterkeys():
        #     rbitem = qt.QRadioButton(self.logic.params.getArtifactLabel(artifactId))
        #     self.artifactsRadioButtonGroup.addButton(rbitem, artifactId)
        #     self.typesLayout.addWidget(rbitem)
        # self.artifactsRadioButtonGroup.buttons()[0].setChecked(True)
        #
        self.typesLayout.setAlignment(SlicerUtil.ALIGNMENT_VERTICAL_TOP)
        self.typesLayout.addStretch()

        # Connections
        self.typesRadioButtonGroup.connect("buttonClicked (QAbstractButton*)",
                                           self.__onTypesRadioButtonClicked__)
        self.subtypesRadioButtonGroup.connect(
            "buttonClicked (QAbstractButton*)",
            self.__onSecondaryRadioButtonClicked__)
        self.regionsRadioButtonGroup.connect(
            "buttonClicked (QAbstractButton*)",
            self.__onSecondaryRadioButtonClicked__)
        # self.artifactsRadioButtonGroup.connect("buttonClicked (QAbstractButton*)", self.__onTypesRadioButtonClicked__)

        self.mainLayout.addWidget(self.radioButtonsFrame, 2, 0, 1, 3,
                                  SlicerUtil.ALIGNMENT_VERTICAL_TOP)

        # Save results button
        self.saveResultsButton = ctk.ctkPushButton()
        self.saveResultsButton.setText("Save results")
        self.saveResultsButton.toolTip = "Save the results labelmap in the specified directory"
        self.saveResultsButton.setIcon(
            qt.QIcon("{0}/Save.png".format(SlicerUtil.CIP_ICON_DIR)))
        self.saveResultsButton.setIconSize(qt.QSize(20, 20))
        self.saveResultsButton.setFixedWidth(135)
        self.mainLayout.addWidget(self.saveResultsButton, 4, 0)
        self.saveResultsButton.connect('clicked()',
                                       self._onSaveResultsButtonClicked_)

        # Save results directory button
        defaultPath = os.path.join(
            SlicerUtil.getSettingsDataFolder(self.moduleName),
            "results")  # Assign a default path for the results
        path = SlicerUtil.settingGetOrSetDefault(self.moduleName,
                                                 "SaveResultsDirectory",
                                                 defaultPath)
        self.saveResultsDirectoryButton = ctk.ctkDirectoryButton()
        self.saveResultsDirectoryButton.directory = path
        self.saveResultsDirectoryButton.setMaximumWidth(440)
        self.mainLayout.addWidget(self.saveResultsDirectoryButton, 4, 1, 1, 2)
        self.saveResultsDirectoryButton.connect(
            "directoryChanged (QString)", self._onSaveResultsDirectoryChanged_)

        self._createSegmentEditorWidget_()

        # MIP viewer (by default it will be hidden)
        self.mipCollapsibleButton = ctk.ctkCollapsibleButton()
        self.mipCollapsibleButton.text = "MIP viewer"
        mipLayout = qt.QVBoxLayout(self.mipCollapsibleButton)
        self.mainLayout.addWidget(self.mipCollapsibleButton)
        self.mipViewer = CIPUI.MIPViewerWidget(mipLayout)
        self.mipCollapsibleButton.setVisible(False)
        self.mipViewer.setup()
        self.mipViewer.isCrosshairEnabled = False
        self.mipCollapsibleButton.collapsed = True

        #####
        # Case navigator
        self.caseNavigatorWidget = None
        if SlicerUtil.isSlicerACILLoaded():
            caseNavigatorAreaCollapsibleButton = ctk.ctkCollapsibleButton()
            caseNavigatorAreaCollapsibleButton.text = "Case navigator"
            self.layout.addWidget(caseNavigatorAreaCollapsibleButton, 0x0020)
            # caseNavigatorLayout = qt.QVBoxLayout(caseNavigatorAreaCollapsibleButton)

            # Add a case list navigator
            from ACIL.ui import CaseNavigatorWidget
            self.caseNavigatorWidget = CaseNavigatorWidget(
                self.moduleName, caseNavigatorAreaCollapsibleButton)
            self.caseNavigatorWidget.setup()
            # Listen for event in order to save the current labelmap before moving to the next case
            self.caseNavigatorWidget.addObservable(
                self.caseNavigatorWidget.EVENT_PRE_NEXT,
                self._checkSaveChanges_)
            self.caseNavigatorWidget.addObservable(
                self.caseNavigatorWidget.EVENT_PRE_PREVIOUS,
                self._checkSaveChanges_)
            self.caseNavigatorWidget.addObservable(
                self.caseNavigatorWidget.EVENT_PRE_LABELMAP_LOAD,
                self._onPreNavigatorLabelmapLoaded_)
            self.caseNavigatorWidget.addObservable(
                self.caseNavigatorWidget.EVENT_LABELMAP_LOADED,
                self._onNavigatorLabelmapLoaded_)

        self.layout.addStretch()

        # Extra Connections
        self._createSceneObservers_()

        self.disableEvents = False
        self.setMainTypeGUIProperties()

    @property
    def currentVolumeLoaded(self):
        return self.volumeSelector.currentNode()

    def _initLogic_(self):
        """Create a new logic object for the plugin"""
        self.logic = CIP_ParenchymaSubtypeTrainingLabellingLogic()

    def _createSceneObservers_(self):
        """
        Create the observers for the scene in this module
        """
        self.observers = []
        # self.observers.append(
        #     slicer.mrmlScene.AddObserver(slicer.vtkMRMLScene.NodeAddedEvent, self.__onNodeAddedObserver__))
        self.observers.append(
            slicer.mrmlScene.AddObserver(slicer.vtkMRMLScene.EndCloseEvent,
                                         self.__onSceneClosed__))

    def saveResultsCurrentNode(self):
        """ Get current active node and save the xml fiducials file
        """
        try:
            d = self.saveResultsDirectoryButton.directory
            if not os.path.isdir(d):
                # Ask the user if he wants to create the folder
                if qt.QMessageBox.question(
                        slicer.util.mainWindow(), "Create directory?",
                        "The directory '{0}' does not exist. Do you want to create it?"
                        .format(d), qt.QMessageBox.Yes
                        | qt.QMessageBox.No) == qt.QMessageBox.Yes:
                    try:
                        os.makedirs(d)
                        # Make sure that everybody has write permissions (sometimes there are problems because of umask)
                        os.chmod(d, 0o777)
                    except:
                        qt.QMessageBox.warning(
                            slicer.util.mainWindow(), 'Directory incorrect',
                            'The folder "{0}" could not be created. Please select a valid directory'
                            .format(d))
                        return
                else:
                    # Abort process
                    SlicerUtil.logDevelop("Saving results process aborted",
                                          includePythonConsole=True)
                    return
                    # self.logic.saveCurrentFiducials(d, self.caseNavigatorWidget, self.uploadFileResult)
                    # qt.QMessageBox.information(slicer.util.mainWindow(), 'Results saved',
                    #                            "The results have been saved succesfully")
            # else:
            if SlicerUtil.isSlicerACILLoaded():
                question = qt.QMessageBox.question(
                    slicer.util.mainWindow(), "Save results remotely?",
                    "Your results will be saved locally. Do you also want to save your results in your remote server? (MAD, etc.)",
                    qt.QMessageBox.Yes | qt.QMessageBox.No
                    | qt.QMessageBox.Cancel)
                if question == qt.QMessageBox.Cancel:
                    return
                saveInRemoteRepo = question == qt.QMessageBox.Yes
            else:
                saveInRemoteRepo = False
            self.logic.saveCurrentFiducials(
                d,
                caseNavigatorWidget=self.caseNavigatorWidget,
                callbackFunction=self.uploadFileResult,
                saveInRemoteRepo=saveInRemoteRepo)
            qt.QMessageBox.information(
                slicer.util.mainWindow(), 'Results saved',
                "The results have been saved succesfully")
        except:
            Util.print_last_exception()
            qt.QMessageBox.critical(
                slicer.util.mainWindow(), "Error when saving the results",
                "Error when saving the results. Please review the console for additional info"
            )

    def uploadFileResult(self, result):
        """Callback method that will be invoked by the CaseNavigator after uploading a file remotely"""
        if result != Util.OK:
            qt.QMessageBox.warning(
                slicer.util.mainWindow(), "Error when uploading fiducials",
                "There was an error when uploading the fiducials file. This doesn't mean that your file wasn't saved locally!\n"
                + "Please review the console for more information")

    @property
    def colorNode(self):
        nodeName = "{}_colorNode".format(self.moduleName)
        colorTableNode = SlicerUtil.getNode(nodeName)
        if colorTableNode is None:
            colorTableNode = self.logic.params.createColormapNode(nodeName)
        return colorTableNode

    def setMIPViewerVisible(self, show):
        self.mipCollapsibleButton.setVisible(show)

    def checkMasterAndLabelMapNodes(self):
        """Set an appropiate MasterNode LabelMapNode to the Editor.
        The options are:
            - There is no masterNode => try to load the one that the user is watching right now, and go on if so.
            - There is masterNode and there is no label map => create a default label map node with the name "MasterNodeName_structuresDetection" and set the StructuresDetectionColorMap
            - There is masterNode and there is label map => check if the name of the label map is "MasterNodeName_structuresDetection".
                - If so: set this one
                - Otherwise: create a new labelmap with the name 'MasterNodeName_structureslabelMap' """
        if self.disableEvents: return  # To avoid infinite loops

        if self.editorWidget.masterVolume:
            masterNode = self.editorWidget.masterVolume
            SlicerUtil.logDevelop(
                "Master node in Editor = " + masterNode.GetName(), True)
        else:
            SlicerUtil.logDevelop(
                "No master node in Editor. Retrieving it from the selector...",
                False)
            masterNode = self.getCurrentGrayscaleNode()

        if not masterNode:
            # There is no any volume node that the user is watching
            SlicerUtil.logDevelop("Still not master node. Exit", False)
            return

        labelmapNode = self.getOrCreateLabelmap(masterNode)
        displayNode = labelmapNode.GetDisplayNode()

        if displayNode:
            displayNode.SetAndObserveColorNodeID(self.colorNode.GetID())
        else:
            SlicerUtil.logDevelop(
                "There is no DisplayNode for label map " +
                labelmapNode.GetName(), True)

        slicer.app.applicationLogic().PropagateVolumeSelection(0)
        SlicerUtil.changeLabelmapOpacity(0.5)

        # Set the right volumes
        self.disableEvents = True
        #self.editorWidget.masterVolume = masterNode
        #self.editorWidget.labelmapVolume = labelmapNode
        # trigger editor events
        self.editorWidget.helper.setVolumes(masterNode, labelmapNode)
        self.disableEvents = False

        slicer.app.applicationLogic().FitSliceToAll()

    def getOrCreateLabelmap(self, masterNode):
        labelmapName = "{0}_{1}".format(masterNode.GetName(),
                                        self.labelmapNodeNameExtension)
        labelmapNode = SlicerUtil.getNode(labelmapName)
        if labelmapNode is None:
            # Create a labelmap for this scalar
            labelmapNode = slicer.modules.volumes.logic(
            ).CreateAndAddLabelVolume(slicer.mrmlScene, masterNode,
                                      labelmapName)
            # Make sure that the labelmap has this name (no suffixes)
            labelmapNode.SetName(labelmapName)
            # Register the labelmap in the case navigator so that it is removed when moving to another case
            if SlicerUtil.isSlicerACILLoaded():
                self.caseNavigatorWidget.registerVolumeId(labelmapNode.GetID())
            SlicerUtil.logDevelop("New label map node created: " +
                                  labelmapName,
                                  includePythonConsole=True)
        else:
            SlicerUtil.logDevelop("Labelmap loaded", includePythonConsole=True)
        return labelmapNode

    def getCurrentGrayscaleNode(self):
        """Get the grayscale node that is currently active in the widget"""
        #return self.editorWidget.masterVolume
        return self.volumeSelector.currentNode()

    def getCurrentLabelMapNode(self):
        """Get the labelmap node that is currently active in the widget"""
        return self.editorWidget.labelmapVolume

    def setCurrentGrayscaleNode(self, node):
        """Get the grayscale node that is currently active in the widget"""
        self.editorWidget.masterVolume = node

    def setCurrentLabelMapNode(self, node):
        """Get the labelmap node that is currently active in the widget"""
        self.editorWidget.labelmapVolume = node

    def setMainTypeGUIProperties(self):
        """Show/Hide the right Radio Buttons and set the right color for drawing based on the selected type-subtype"""
        # Load the subtypes for this type
        subtypesDict = self.logic.getSubtypes(
            self.typesRadioButtonGroup.checkedId())

        # Hide/Show the subtypes for this type
        for b in self.subtypesRadioButtonGroup.buttons():
            id = self.subtypesRadioButtonGroup.id(b)
            if id in subtypesDict:
                b.show()
            else:
                b.hide()

        # if self.artifactsRadioButtonGroup.checkedId() == 0:
        # No artifact. Check first element by default
        self.subtypesRadioButtonGroup.buttons()[0].setChecked(True)

        # Set the right color in the colormap
        typeId = self.typesRadioButtonGroup.checkedId()
        regionId = self.regionsRadioButtonGroup.checkedId()
        colorId = (typeId << 8) + regionId
        self.editorWidget.toolsColor.colorSpin.setValue(colorId)
        self.editorWidget.setActiveEffect("PaintEffect")

    def setSecondaryTypeGUIProperties(self):
        subtype = self.subtypesRadioButtonGroup.checkedId()
        t = self.typesRadioButtonGroup.checkedId(
        ) if subtype == 0 else self.subtypesRadioButtonGroup.checkedId()
        # if subtype == 0:
        #     # Subtype "Any". Select main type
        #     self.editorWidget.toolsColor.colorSpin.setValue(self.typesRadioButtonGroup.checkedId())
        # else:
        #     # Select subtype
        #     self.editorWidget.toolsColor.colorSpin.setValue(self.subtypesRadioButtonGroup.checkedId())
        regionId = self.regionsRadioButtonGroup.checkedId()
        colorId = (t << 8) + regionId
        self.editorWidget.toolsColor.colorSpin.setValue(colorId)
        self.editorWidget.setActiveEffect("PaintEffect")

    def saveResults(self):
        try:
            if SlicerUtil.isSlicerACILLoaded():
                saveResultsRemotely = qt.QMessageBox.question(
                    slicer.util.mainWindow(), "Save volume remotely?",
                    "Do you want to save the results remotely?",
                    qt.QMessageBox.Yes
                    | qt.QMessageBox.No) == qt.QMessageBox.Yes
            else:
                saveResultsRemotely = False
            # First, save locally to the results directory (as a backup)
            labelmap = self.getCurrentLabelMapNode()
            localPath = os.path.join(self.saveResultsDirectoryButton.directory,
                                     labelmap.GetName() + ".nrrd")
            if saveResultsRemotely:
                self.caseNavigatorWidget.uploadVolume(
                    labelmap,
                    callbackFunction=self._uploadFileCallback_,
                    localPath=localPath)
            else:
                slicer.util.saveNode(labelmap, localPath)
                slicer.util.infoDisplay(
                    "Results saved to '{}'".format(localPath))

        except Exception as ex:
            Util.print_last_exception()
            slicer.util.errorDisplay(ex.message)

    ##############
    # Aux methods
    ##############
    def _onMainVolumeChanged_(self, newVolumeNode):
        """ A volume was changed in the main volume selector
        :param newVolumeNode:
        :return:
        """
        if not self.disableEvents:
            self.setCurrentGrayscaleNode(newVolumeNode)
            self.checkMasterAndLabelMapNodes()

    def _onPreNavigatorLabelmapLoaded_(self, volumeNodeName):
        self.labelmapToBeRemoved = SlicerUtil.getNode(volumeNodeName)

    def _onNavigatorLabelmapLoaded_(self, volumeNode, region, type):
        """When a labelmap is loaded in the CaseNavigator, remove possible preexisting nodes"""
        if self.labelmapToBeRemoved:
            slicer.mrmlScene.RemoveNode(self.labelmapToBeRemoved)
            self.labelmapToBeRemoved = None

        self.checkMasterAndLabelMapNodes()

    def _createSegmentEditorWidget_(self):
        """Create and initialize a customize Slicer Editor which contains just some the tools that we need for the segmentation"""

        import qSlicerSegmentationsModuleWidgetsPythonQt
        self.segmentEditorWidget = qSlicerSegmentationsModuleWidgetsPythonQt.qMRMLSegmentEditorWidget(
        )
        self.segmentEditorWidget.setMaximumNumberOfUndoStates(10)
        self.segmentEditorWidget.setMRMLScene(slicer.mrmlScene)
        self.segmentEditorWidget.unorderedEffectsVisible = False
        self.segmentEditorWidget.setEffectNameOrder([
            'Paint', 'Draw', 'Erase', 'Threshold', 'Grow from seeds',
            'Scissors'
        ])
        self.layout.addWidget(self.segmentEditorWidget)

        # Collapse Volumes selector by default
        #self.editorWidget.volumes.collapsed = True

        # Remove current listeners for helper box and override them
        #self.editorWidget.helper.masterSelector.disconnect("currentNodeChanged(vtkMRMLNode*)")
        #self.editorWidget.helper.mergeSelector.disconnect("currentNodeChanged(vtkMRMLNode*)")
        # Force to select always a node. It is important to do this at this point, when the events are disconnected,
        # because otherwise the editor would display the color selector (just noisy for the user)
        #self.editorWidget.helper.masterSelector.noneEnabled = False
        # Listen to the event when there is a Master Node selected in the HelperBox
        #self.editorWidget.helper.masterSelector.connect("currentNodeChanged(vtkMRMLNode*)", self._onMasterNodeSelect_)

    def _collapseEditorWidget_(self, collapsed=True):
        """Collapse/expand the items in EditorWidget"""
        self.editorWidget.volumes.collapsed = collapsed
        self.editorWidget.editLabelMapsFrame.collapsed = collapsed

    def _uploadFileCallback_(self, result):
        """
        Callback after uploading a file (xml/labelmap) to the remote server
        @param result:
        """
        if result == Util.OK:
            qt.QMessageBox.information(slicer.util.mainWindow(),
                                       "Results saved successfully",
                                       "Results saved successfully")
        else:
            qt.QMessageBox.warning(
                slicer.util.mainWindow(), "Error when uploading the data",
                "There was an error when uploading the results file to the remote server.\n "
                "This doesn't mean that your file wasn't saved locally!\n"
                "Please review the console for more information")

    def _checkSaveChanges_(self):
        """ New volume loaded in the scene in some way.
        If it's really a new volume, try to save and close the current one
        @param newVolumeNode:
        """
        if self.currentVolumeLoaded is not None:
            # Ask the user if he wants to save the previously loaded volume
            if qt.QMessageBox.question(
                    slicer.util.mainWindow(), "Save results?",
                    "Do you want to save changes for volume {0}?".format(
                        self.currentVolumeLoaded.GetName()), qt.QMessageBox.Yes
                    | qt.QMessageBox.No) == qt.QMessageBox.Yes:
                self.saveResults()

    def _onClearLabelmapButtonClicked_(self):
        if qt.QMessageBox.question(
                slicer.util.mainWindow(), "Clear labelmap?",
                "Are you sure you want to clear the current labelmap? (THIS OPERATION CANNOT BE UNDONE)",
                qt.QMessageBox.Yes | qt.QMessageBox.No) == qt.QMessageBox.Yes:
            SlicerUtil.clearVolume(self.getCurrentLabelMapNode())

    #########
    # Events
    #########
    def enter(self):
        """Method that is invoked when we switch to the module in slicer user interface"""
        self.disableEvents = False
        if self.firstLoad:
            self.firstLoad = False
        else:
            self.checkMasterAndLabelMapNodes()
            self.editorWidget.helper.masterSelector.connect(
                "currentNodeChanged(vtkMRMLNode*)", self._onMasterNodeSelect_)

    def _onMasterNodeSelect_(self, node):
        if node:
            nodeName = node.GetName()
            if self.getCurrentGrayscaleNode(
            ) and self.getCurrentGrayscaleNode().GetName() != nodeName:
                SlicerUtil.logDevelop(
                    "There was a selection of a new master node: {0}. Previous: {1}. We will invoke checkMasterAndLabelMapNodes"
                    .format(node.GetName(),
                            self.editorWidget.masterVolume.GetName()),
                    includePythonConsole=True)
                # Update Editor Master node to perform the needed actions.
                # We don't use "setVolumes" function because the interface must not be refeshed yet (it will be in checkMasterAndLabelMapNodes)
                self.setCurrentGrayscaleNode(node)
                # Remove label node to refresh the values properly
                self.setCurrentLabelMapNode(None)
                self.checkMasterAndLabelMapNodes()
        else:
            SlicerUtil.logDevelop(
                "No master node selected. Trying to remove label map", False)
            self.editorWidget.cleanVolumes()
        self.setMainTypeGUIProperties()

    def __onTypesRadioButtonClicked__(self, button):
        """ One of the radio buttons has been pressed
        :param button:
        :return:
        """
        self.setMainTypeGUIProperties()

    def __onSecondaryRadioButtonClicked__(self, button):
        """ One of the subtype radio buttons has been pressed
        :param button:
        :return:
        """
        self.setSecondaryTypeGUIProperties()

    def _onSaveResultsDirectoryChanged_(self, directory):
        SlicerUtil.setSetting(self.moduleName, "SaveResultsDirectory",
                              directory)

    def _onSaveResultsButtonClicked_(self):
        self.saveResults()

    def __onNavigatorCaseChange__(self):
        self._checkSaveChanges_()

    def __onSceneClosed__(self, arg1, arg2):
        self.pendingChangesIdsList = []
        self.logic = CIP_ParenchymaSubtypeTrainingLabellingLogic()

    def exit(self):
        self.editorWidget.helper.masterSelector.disconnect(
            "currentNodeChanged(vtkMRMLNode*)")
        self.disableEvents = True

    def cleanup(self):
        pass
Exemple #6
0
    def setup(self):
        """This is called one time when the module GUI is initialized
        """
        ScriptedLoadableModuleWidget.setup(self)

        # Create objects that can be used anywhere in the module. Example: in most cases there should be just one
        # object of the logic class
        self._initLogic_()
        self.currentVolumeLoaded = None
        self.blockNodeEvents = False

        ##########
        # Main area
        self.mainAreaCollapsibleButton = ctk.ctkCollapsibleButton()
        self.mainAreaCollapsibleButton.text = "Main area"
        self.layout.addWidget(self.mainAreaCollapsibleButton,
                              SlicerUtil.ALIGNMENT_VERTICAL_TOP)
        self.mainLayout = qt.QGridLayout(self.mainAreaCollapsibleButton)

        # Node selector
        volumeLabel = qt.QLabel("Active volume: ")
        volumeLabel.setStyleSheet("margin-left:5px")
        self.mainLayout.addWidget(volumeLabel, 0, 0)
        self.volumeSelector = slicer.qMRMLNodeComboBox()
        self.volumeSelector.nodeTypes = ("vtkMRMLScalarVolumeNode", "")
        self.volumeSelector.selectNodeUponCreation = True
        self.volumeSelector.autoFillBackground = True
        self.volumeSelector.addEnabled = False
        self.volumeSelector.noneEnabled = False
        self.volumeSelector.removeEnabled = False
        self.volumeSelector.showHidden = False
        self.volumeSelector.showChildNodeTypes = False
        self.volumeSelector.setMRMLScene(slicer.mrmlScene)
        self.volumeSelector.setFixedWidth(250)
        self.volumeSelector.setStyleSheet("margin: 15px 0")
        #self.volumeSelector.selectNodeUponCreation = False
        self.mainLayout.addWidget(self.volumeSelector, 0, 1, 1, 3)
        self.volumeSelector.connect('currentNodeChanged(vtkMRMLNode*)',
                                    self.__onCurrentNodeChanged__)

        # Radio buttons frame. This will be filled by every child module
        self.radioButtonsFrame = qt.QFrame()
        self.mainLayout.addWidget(self.radioButtonsFrame, 2, 0, 1, 3,
                                  SlicerUtil.ALIGNMENT_VERTICAL_TOP)

        # Load caselist button
        self.loadButton = ctk.ctkPushButton()
        self.loadButton.text = "Load fiducials file"
        self.loadButton.setIcon(
            qt.QIcon("{0}/open_file.png".format(SlicerUtil.CIP_ICON_DIR)))
        self.loadButton.setIconSize(qt.QSize(20, 20))
        self.loadButton.setFixedWidth(135)
        self.mainLayout.addWidget(self.loadButton, 3, 0)
        self.loadButton.connect('clicked()', self.openFiducialsFile)

        # Remove fiducial button
        self.removeLastFiducialButton = ctk.ctkPushButton()
        self.removeLastFiducialButton.text = "Remove last fiducial"
        self.removeLastFiducialButton.toolTip = "Remove the last fiducial added"
        self.removeLastFiducialButton.setIcon(
            qt.QIcon("{0}/delete.png".format(SlicerUtil.CIP_ICON_DIR)))
        self.removeLastFiducialButton.setIconSize(qt.QSize(20, 20))
        self.removeLastFiducialButton.setFixedWidth(200)
        self.mainLayout.addWidget(self.removeLastFiducialButton, 3, 1)
        self.removeLastFiducialButton.connect(
            'clicked()', self.__onRemoveLastFiducialButtonClicked__)

        # Save results button
        self.saveResultsButton = ctk.ctkPushButton()
        self.saveResultsButton.setText("Save markups")
        self.saveResultsButton.toolTip = "Save the markups in the specified directory"
        self.saveResultsButton.setIcon(
            qt.QIcon("{0}/Save.png".format(SlicerUtil.CIP_ICON_DIR)))
        self.saveResultsButton.setIconSize(qt.QSize(20, 20))
        self.saveResultsButton.setFixedWidth(135)
        self.mainLayout.addWidget(self.saveResultsButton, 4, 0)
        self.saveResultsButton.connect('clicked()',
                                       self.__onSaveResultsButtonClicked__)

        # Save results directory button
        defaultPath = os.path.join(
            SlicerUtil.getSettingsDataFolder(self.moduleName),
            "results")  # Assign a default path for the results
        path = SlicerUtil.settingGetOrSetDefault(self.moduleName,
                                                 "SaveResultsDirectory",
                                                 defaultPath)
        self.saveResultsDirectoryButton = ctk.ctkDirectoryButton()
        self.saveResultsDirectoryButton.directory = path
        self.saveResultsDirectoryButton.setMaximumWidth(375)
        self.mainLayout.addWidget(self.saveResultsDirectoryButton, 4, 1, 1, 2)
        self.saveResultsDirectoryButton.connect(
            "directoryChanged (QString)",
            self.__onSaveResultsDirectoryChanged__)

        #####
        # Case navigator
        self.caseNavigatorWidget = None
        if SlicerUtil.isSlicerACILLoaded():
            caseNavigatorAreaCollapsibleButton = ctk.ctkCollapsibleButton()
            caseNavigatorAreaCollapsibleButton.text = "Case navigator"
            self.layout.addWidget(caseNavigatorAreaCollapsibleButton, 0x0020)
            # caseNavigatorLayout = qt.QVBoxLayout(caseNavigatorAreaCollapsibleButton)

            # Add a case list navigator
            from ACIL.ui import CaseNavigatorWidget
            self.caseNavigatorWidget = CaseNavigatorWidget(
                self.moduleName, caseNavigatorAreaCollapsibleButton)
            self.caseNavigatorWidget.setup()
            # Listen for the event of loading a new labelmap
            # self.caseNavigatorWidget.addObservable(self.caseNavigatorWidget.EVENT_LABELMAP_LOADED, self.__onNewILDClassificationLabelmapLoaded__)
            self.caseNavigatorWidget.addObservable(
                self.caseNavigatorWidget.EVENT_BUNDLE_CASE_FINISHED,
                self.__onFinishCaseBundleLoad__)

        self.layout.addStretch()

        # Extra Connections
        self._createSceneObservers_()
Exemple #7
0
class CIP_ParenchymaSubtypeTrainingWidget(ScriptedLoadableModuleWidget):
    """Uses ScriptedLoadableModuleWidget base class, available at:
    https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
    """
    @property
    def moduleName(self):
        return "CIP_ParenchymaSubtypeTraining"

    def __init__(self, parent):
        ScriptedLoadableModuleWidget.__init__(self, parent)

        from functools import partial

        def __onNodeAddedObserver__(self, caller, eventId, callData):
            """Node added to the Slicer scene"""
            if callData.GetClassName() == 'vtkMRMLScalarVolumeNode' \
                    and slicer.util.mainWindow().moduleSelector().selectedModule == self.moduleName:
                self.__onNewVolumeLoaded__(callData)
            # elif callData.GetClassName() == 'vtkMRMLLabelMapVolumeNode':
            #     self.__onNewLabelmapLoaded__(callData)

        self.__onNodeAddedObserver__ = partial(__onNodeAddedObserver__, self)
        self.__onNodeAddedObserver__.CallDataType = vtk.VTK_OBJECT

    def setup(self):
        """This is called one time when the module GUI is initialized
        """
        ScriptedLoadableModuleWidget.setup(self)

        # Create objects that can be used anywhere in the module. Example: in most cases there should be just one
        # object of the logic class
        self.logic = CIP_ParenchymaSubtypeTrainingLogic()
        self.currentVolumeLoaded = None
        self.blockNodeEvents = False

        ##########
        # Main area
        self.mainAreaCollapsibleButton = ctk.ctkCollapsibleButton()
        self.mainAreaCollapsibleButton.text = "Main area"
        self.layout.addWidget(self.mainAreaCollapsibleButton,
                              SlicerUtil.ALIGNMENT_VERTICAL_TOP)
        self.mainLayout = qt.QGridLayout(self.mainAreaCollapsibleButton)

        # Node selector
        volumeLabel = qt.QLabel("Active volume: ")
        volumeLabel.setStyleSheet("margin-left:5px")
        self.mainLayout.addWidget(volumeLabel, 0, 0)
        self.volumeSelector = slicer.qMRMLNodeComboBox()
        self.volumeSelector.nodeTypes = ("vtkMRMLScalarVolumeNode", "")
        self.volumeSelector.selectNodeUponCreation = True
        self.volumeSelector.autoFillBackground = True
        self.volumeSelector.addEnabled = False
        self.volumeSelector.noneEnabled = False
        self.volumeSelector.removeEnabled = False
        self.volumeSelector.showHidden = False
        self.volumeSelector.showChildNodeTypes = False
        self.volumeSelector.setMRMLScene(slicer.mrmlScene)
        self.volumeSelector.setFixedWidth(250)
        self.volumeSelector.setStyleSheet("margin: 15px 0")
        #self.volumeSelector.selectNodeUponCreation = False
        self.mainLayout.addWidget(self.volumeSelector, 0, 1, 1, 3)

        labelsStyle = "font-weight: bold; margin: 0 0 5px 5px;"
        # Types Radio Buttons
        typesLabel = qt.QLabel("Select the type")
        typesLabel.setStyleSheet(labelsStyle)
        typesLabel.setFixedHeight(15)
        self.mainLayout.addWidget(typesLabel, 1, 0)
        self.typesFrame = qt.QFrame()
        self.typesLayout = qt.QVBoxLayout(self.typesFrame)
        self.mainLayout.addWidget(self.typesFrame, 2, 0,
                                  SlicerUtil.ALIGNMENT_VERTICAL_TOP)
        self.typesRadioButtonGroup = qt.QButtonGroup()
        for key in self.logic.params.mainTypes.iterkeys():
            rbitem = qt.QRadioButton(self.logic.params.getMainTypeLabel(key))
            self.typesRadioButtonGroup.addButton(rbitem, key)
            self.typesLayout.addWidget(rbitem)
        self.typesRadioButtonGroup.buttons()[0].setChecked(True)

        # Subtypes Radio buttons
        subtypesLabel = qt.QLabel("Select the subtype")
        subtypesLabel.setStyleSheet(labelsStyle)
        subtypesLabel.setFixedHeight(15)
        self.mainLayout.addWidget(subtypesLabel, 1, 1)
        self.subtypesRadioButtonGroup = qt.QButtonGroup()
        self.subtypesFrame = qt.QFrame()
        self.subtypesFrame.setMinimumHeight(275)
        self.subtypesLayout = qt.QVBoxLayout(self.subtypesFrame)
        self.subtypesLayout.setAlignment(SlicerUtil.ALIGNMENT_VERTICAL_TOP)
        self.subtypesLayout.setStretch(0, 0)
        self.mainLayout.addWidget(
            self.subtypesFrame, 2, 1,
            SlicerUtil.ALIGNMENT_VERTICAL_TOP)  # Put the frame in the top
        # The content will be loaded dynamically every time the main type is modified

        # Artifact radio buttons
        self.artifactsLabel = qt.QLabel("Artifact")
        self.artifactsLabel.setStyleSheet(labelsStyle)
        self.artifactsLabel.setFixedHeight(15)
        self.mainLayout.addWidget(self.artifactsLabel, 1, 2)
        #self.mainLayout.addWidget(qt.QLabel("Select the artifact"), 1, 1)
        self.artifactsRadioButtonGroup = qt.QButtonGroup()
        self.artifactsFrame = qt.QFrame()
        self.artifactsLayout = qt.QVBoxLayout(self.artifactsFrame)
        self.mainLayout.addWidget(self.artifactsFrame, 2, 2,
                                  SlicerUtil.ALIGNMENT_VERTICAL_TOP)
        self.artifactsRadioButtonGroup = qt.QButtonGroup()
        for artifactId in self.logic.params.artifacts.iterkeys():
            rbitem = qt.QRadioButton(
                self.logic.params.getArtifactLabel(artifactId))
            self.artifactsRadioButtonGroup.addButton(rbitem, artifactId)
            self.artifactsLayout.addWidget(rbitem)
        self.artifactsRadioButtonGroup.buttons()[0].setChecked(True)

        # Load caselist button
        self.loadButton = ctk.ctkPushButton()
        self.loadButton.text = "Load fiducials file"
        self.loadButton.setIcon(
            qt.QIcon("{0}/open_file.png".format(SlicerUtil.CIP_ICON_DIR)))
        self.loadButton.setIconSize(qt.QSize(20, 20))
        self.loadButton.setFixedWidth(135)
        self.mainLayout.addWidget(self.loadButton, 3, 0)

        # Remove fiducial button
        self.removeLastFiducialButton = ctk.ctkPushButton()
        self.removeLastFiducialButton.text = "Remove last fiducial"
        self.removeLastFiducialButton.toolTip = "Remove the last fiducial added"
        self.removeLastFiducialButton.setIcon(
            qt.QIcon("{0}/delete.png".format(SlicerUtil.CIP_ICON_DIR)))
        self.removeLastFiducialButton.setIconSize(qt.QSize(20, 20))
        self.removeLastFiducialButton.setFixedWidth(200)
        self.mainLayout.addWidget(self.removeLastFiducialButton, 3, 1)

        # Save results button
        self.saveResultsButton = ctk.ctkPushButton()
        self.saveResultsButton.setText("Save markups")
        self.saveResultsButton.toolTip = "Save the markups in the specified directory"
        self.saveResultsButton.setIcon(
            qt.QIcon("{0}/Save.png".format(SlicerUtil.CIP_ICON_DIR)))
        self.saveResultsButton.setIconSize(qt.QSize(20, 20))
        self.mainLayout.addWidget(self.saveResultsButton, 4, 0)

        # Save results directory button
        defaultPath = os.path.join(
            SlicerUtil.getSettingsDataFolder(self.moduleName),
            "results")  # Assign a default path for the results
        path = SlicerUtil.settingGetOrSetDefault(self.moduleName,
                                                 "SaveResultsDirectory",
                                                 defaultPath)
        self.saveResultsDirectoryButton = ctk.ctkDirectoryButton()
        self.saveResultsDirectoryButton.directory = path
        self.saveResultsDirectoryButton.setMaximumWidth(375)
        self.mainLayout.addWidget(self.saveResultsDirectoryButton, 4, 1, 1, 2)

        #####
        # Case navigator
        self.caseNavigatorWidget = None
        if SlicerUtil.isSlicerACILLoaded():
            caseNavigatorAreaCollapsibleButton = ctk.ctkCollapsibleButton()
            caseNavigatorAreaCollapsibleButton.text = "Case navigator"
            self.layout.addWidget(caseNavigatorAreaCollapsibleButton, 0x0020)
            # caseNavigatorLayout = qt.QVBoxLayout(caseNavigatorAreaCollapsibleButton)

            # Add a case list navigator
            from ACIL.ui import CaseNavigatorWidget
            self.caseNavigatorWidget = CaseNavigatorWidget(
                self.moduleName, caseNavigatorAreaCollapsibleButton)
            self.caseNavigatorWidget.setup()
            # Listen for the event of loading a new labelmap
            self.caseNavigatorWidget.addObservable(
                self.caseNavigatorWidget.EVENT_LABELMAP_LOADED,
                self.__onNewILDClassificationLabelmapLoaded__)

        self.layout.addStretch()

        self.updateState()

        # Connections
        self.typesRadioButtonGroup.connect("buttonClicked (QAbstractButton*)",
                                           self.__onTypesRadioButtonClicked__)
        self.subtypesRadioButtonGroup.connect(
            "buttonClicked (QAbstractButton*)",
            self.__onSubtypesRadioButtonClicked__)
        self.artifactsRadioButtonGroup.connect(
            "buttonClicked (QAbstractButton*)",
            self.__onSubtypesRadioButtonClicked__)

        self.volumeSelector.connect('currentNodeChanged(vtkMRMLNode*)',
                                    self.__onCurrentNodeChanged__)
        self.loadButton.connect('clicked()', self.openFiducialsFile)
        self.removeLastFiducialButton.connect(
            'clicked()', self.__onRemoveLastFiducialButtonClicked__)
        # self.saveResultsOpenDirectoryDialogButton.connect('clicked()', self.onOpenDirectoryDialogButtonClicked)
        self.saveResultsDirectoryButton.connect(
            "directoryChanged (QString)",
            self.__onSaveResultsDirectoryChanged__)
        self.saveResultsButton.connect('clicked()',
                                       self.__onSaveResultsButtonClicked__)

        self.observers = []
        self.observers.append(
            slicer.mrmlScene.AddObserver(slicer.vtkMRMLScene.NodeAddedEvent,
                                         self.__onNodeAddedObserver__))
        self.observers.append(
            slicer.mrmlScene.AddObserver(slicer.vtkMRMLScene.EndCloseEvent,
                                         self.__onSceneClosed__))

    def updateState(self):
        """ Refresh the markups state, activate the right fiducials list node (depending on the
        current selected type) and creates it when necessary
        :return:
        """
        # Load the subtypes for this type
        subtypesDict = self.logic.getSubtypes(
            self.typesRadioButtonGroup.checkedId())
        # Remove all the existing buttons
        for b in self.subtypesRadioButtonGroup.buttons():
            b.hide()
            b.delete()
        # Add all the subtypes with the full description
        for subtype in subtypesDict.iterkeys():
            rbitem = qt.QRadioButton(
                self.logic.getSubtypeFullDescription(subtype))
            self.subtypesRadioButtonGroup.addButton(rbitem, subtype)
            self.subtypesLayout.addWidget(rbitem,
                                          SlicerUtil.ALIGNMENT_VERTICAL_TOP)
        # Check first element by default
        self.subtypesRadioButtonGroup.buttons()[0].setChecked(True)

        # Set the correct state for fiducials
        if self.currentVolumeLoaded is not None:
            self.logic.setActiveFiducialsListNode(
                self.currentVolumeLoaded,
                self.typesRadioButtonGroup.checkedId(),
                self.subtypesRadioButtonGroup.checkedId(),
                self.artifactsRadioButtonGroup.checkedId())

    def saveResultsCurrentNode(self):
        """ Get current active node and save the xml fiducials file
        """
        try:
            d = self.saveResultsDirectoryButton.directory
            if not os.path.isdir(d):
                # Ask the user if he wants to create the folder
                if qt.QMessageBox.question(
                        slicer.util.mainWindow(), "Create directory?",
                        "The directory '{0}' does not exist. Do you want to create it?"
                        .format(d), qt.QMessageBox.Yes
                        | qt.QMessageBox.No) == qt.QMessageBox.Yes:
                    try:
                        os.makedirs(d)
                        # Make sure that everybody has write permissions (sometimes there are problems because of umask)
                        os.chmod(d, 0777)
                    except:
                        qt.QMessageBox.warning(
                            slicer.util.mainWindow(), 'Directory incorrect',
                            'The folder "{0}" could not be created. Please select a valid directory'
                            .format(d))
                        return
                    self.logic.saveCurrentFiducials(d,
                                                    self.caseNavigatorWidget,
                                                    self.uploadFileResult)
                    qt.QMessageBox.information(
                        slicer.util.mainWindow(), 'Results saved',
                        "The results have been saved succesfully")
            else:
                self.logic.saveCurrentFiducials(d, self.caseNavigatorWidget,
                                                self.uploadFileResult)
                qt.QMessageBox.information(
                    slicer.util.mainWindow(), 'Results saved',
                    "The results have been saved succesfully")
        except:
            Util.print_last_exception()
            qt.QMessageBox.critical(
                slicer.util.mainWindow(), "Error when saving the results",
                "Error when saving the results. Please review the console for additional info"
            )

    def uploadFileResult(self, result):
        if result != Util.OK:
            qt.QMessageBox.warning(
                slicer.util.mainWindow(), "Error when uploading fiducials",
                "There was an error when uploading the fiducials file. This doesn't mean that your file wasn't saved locally!\n"
                + "Please review the console for more information")

    def openFiducialsFile(self):
        volumeNode = self.volumeSelector.currentNode()
        if volumeNode is None:
            qt.QMessageBox.warning(slicer.util.mainWindow(), 'Select a volume',
                                   'Please load a volume first')
            return

        f = qt.QFileDialog.getOpenFileName()
        if f:
            self.logic.loadFiducialsXml(volumeNode, f)
            self.saveResultsDirectoryButton.directory = os.path.dirname(f)
            qt.QMessageBox.information(slicer.util.mainWindow(), "File loaded",
                                       "File loaded successfully")

    ## PRIVATE METHODS
    def __checkNewVolume__(self, newVolumeNode):
        """ New volume loaded in the scene in some way.
        If it's really a new volume, try to save and close the current one
        @param newVolumeNode:
        """
        if self.blockNodeEvents:
            return
        self.blockNodeEvents = True
        volume = self.currentVolumeLoaded
        if volume is not None and newVolumeNode is not None \
                and newVolumeNode.GetID() != volume.GetID()  \
                and not self.logic.isVolumeSaved(volume.GetName()):
            # Ask the user if he wants to save the previously loaded volume
            if qt.QMessageBox.question(
                    slicer.util.mainWindow(), "Save results?",
                    "The fiducials for the volume '{0}' have not been saved. Do you want to save them?"
                    .format(volume.GetName()), qt.QMessageBox.Yes
                    | qt.QMessageBox.No) == qt.QMessageBox.Yes:
                self.saveResultsCurrentNode()
        # Remove all the previously existing nodes
        if self.currentVolumeLoaded is not None and newVolumeNode != self.currentVolumeLoaded:
            # Remove previously existing node
            self.logic.removeMarkupsAndNode(self.currentVolumeLoaded)
        if self.caseNavigatorWidget is not None and newVolumeNode is not None:
            # Try to load a previously existing fiducials file downloaded with the ACIL case navigator
            fiducialsFileName = newVolumeNode.GetName(
            ) + Util.file_conventions_extensions[
                "ParenchymaTrainingFiducialsXml"]
            fiducialsNavigatorFilePath = self.caseNavigatorWidget.logic.getFilePath(
                fiducialsFileName)
            if os.path.exists(fiducialsNavigatorFilePath):
                # The fiducials file was downloaded with the navigator
                self.logic.loadFiducialsXml(newVolumeNode,
                                            fiducialsNavigatorFilePath)

        if newVolumeNode is not None:
            SlicerUtil.setActiveVolumeId(newVolumeNode.GetID())
            SlicerUtil.setFiducialsCursorMode(True, True)

        self.currentVolumeLoaded = newVolumeNode
        self.updateState()
        self.blockNodeEvents = False

    def __getColorTable__(self):
        """ Color table for this module for a better labelmap visualization.
        This method is optimized for ILD classification, but it can easily be extended for different visualizations"""
        colorTableNode = slicer.util.getNode("CIP_ILDClassification_ColorMap*")
        if colorTableNode is None:
            # Load the node from disk
            p = os.path.join(os.path.dirname(os.path.realpath(__file__)),
                             "Resources/CIP_ILDClassification_ColorMap.ctbl")
            colorTableNode = slicer.modules.colors.logic().LoadColorFile(p)
        return colorTableNode

    ## EVENTS
    def enter(self):
        """This is invoked every time that we select this module as the active module in Slicer (not only the first time)"""
        self.blockNodeEvents = False
        # if len(self.observers) == 0:
        #     self.observers.append(slicer.mrmlScene.AddObserver(slicer.vtkMRMLScene.NodeAddedEvent, self.__onNodeAddedObserver__))
        #     self.observers.append(slicer.mrmlScene.AddObserver(slicer.vtkMRMLScene.EndCloseEvent, self.__onSceneClosed__))
        SlicerUtil.setFiducialsCursorMode(True, True)

        if self.volumeSelector.currentNodeId != "":
            SlicerUtil.setActiveVolumeId(self.volumeSelector.currentNodeId)
            self.currentVolumeLoaded = slicer.mrmlScene.GetNodeByID(
                self.volumeSelector.currentNodeId)
            self.updateState()

    def exit(self):
        """This is invoked every time that we switch to another module (not only when Slicer is closed)."""
        try:
            self.blockNodeEvents = True
            SlicerUtil.setFiducialsCursorMode(False)
            # for observer in self.observers:
            #     slicer.mrmlScene.RemoveObserver(observer)
            #     self.observers.remove(observer)
        except:
            pass

    def cleanup(self):
        """This is invoked as a destructor of the GUI when the module is no longer going to be used"""
        try:
            for observer in self.observers:
                slicer.mrmlScene.RemoveObserver(observer)
                self.observers.remove(observer)
        except:
            pass

    def __onNewVolumeLoaded__(self, newVolumeNode):
        """ Added a new node in the scene
        :param newVolumeNode:
        :return:
        """
        # Filter the name of the volume to remove possible suffixes added by Slicer
        filteredName = SlicerUtil.filterVolumeName(newVolumeNode.GetName())
        newVolumeNode.SetName(filteredName)
        self.__checkNewVolume__(newVolumeNode)
        self.blockNodeEvents = True
        self.volumeSelector.setCurrentNode(newVolumeNode)
        self.blockNodeEvents = False

    def __onNewILDClassificationLabelmapLoaded__(self, labelmapNode, split1,
                                                 split2):
        """ Load a new ILD classification labelmap volume.
        If the labelmap is a known labelmap type, set the right colors and opacity
        @param labelmapNode:
        """
        if SlicerUtil.isExtensionMatch(labelmapNode,
                                       "ILDClassificationLabelmap"):
            colorNode = self.__getColorTable__()
            displayNode = labelmapNode.GetDisplayNode()
            displayNode.SetAndObserveColorNodeID(colorNode.GetID())
            # Change Opacity
            SlicerUtil.displayLabelmapVolume(labelmapNode.GetID(), 0.3)

    def __onCurrentNodeChanged__(self, volumeNode):
        self.__checkNewVolume__(volumeNode)
        # if volumeNode:
        #     #self.logic.reset(volumeNode)
        #     SlicerUtil.setActiveVolumeId(volumeNode.GetID())
        #     self.updateState()

    def __onTypesRadioButtonClicked__(self, button):
        """ One of the radio buttons has been pressed
        :param button:
        :return:
        """
        self.updateState()

    def __onSubtypesRadioButtonClicked__(self, button):
        """ One of the subtype radio buttons has been pressed
        :param button:
        :return:
        """
        selectedVolume = self.volumeSelector.currentNode()
        if selectedVolume is not None:
            self.logic.setActiveFiducialsListNode(
                selectedVolume, self.typesRadioButtonGroup.checkedId(),
                self.subtypesRadioButtonGroup.checkedId(),
                self.artifactsRadioButtonGroup.checkedId())

    def __onRemoveLastFiducialButtonClicked__(self):
        self.logic.removeLastMarkup()

    def __onSaveResultsDirectoryChanged__(self, directory):
        # f = qt.QFileDialog.getExistingDirectory()
        # if f:
        #     self.saveResultsDirectoryText.setText(f)
        SlicerUtil.setSetting(self.moduleName, "SaveResultsDirectory",
                              directory)

    def __onSaveResultsButtonClicked__(self):
        self.saveResultsCurrentNode()

    def __onSceneClosed__(self, arg1, arg2):
        self.currentVolumeLoaded = None
        self.logic = CIP_ParenchymaSubtypeTrainingLogic()
Exemple #8
0
class CIP_PointsLabellingWidget(ScriptedLoadableModuleWidget):
    """Uses ScriptedLoadableModuleWidget base class, available at:
    https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
    """
    @property
    def moduleName(self):
        # TODO: get it from file name
        return "CIP_PointsLabelling"

    def __init__(self, parent):
        ScriptedLoadableModuleWidget.__init__(self, parent)

        from functools import partial

        def __onNodeAddedObserver__(self, caller, eventId, callData):
            """Node added to the Slicer scene"""
            if callData.GetClassName() == 'vtkMRMLScalarVolumeNode' \
                    and slicer.util.mainWindow().moduleSelector().selectedModule == self.moduleName:
                self.__onNewVolumeLoaded__(callData)
            # elif callData.GetClassName() == 'vtkMRMLLabelMapVolumeNode':
            #     self.__onNewLabelmapLoaded__(callData)

        self.__onNodeAddedObserver__ = partial(__onNodeAddedObserver__, self)
        self.__onNodeAddedObserver__.CallDataType = vtk.VTK_OBJECT

    def _initLogic_(self):
        """Create a new logic object for the plugin"""
        self.logic = CIP_PointsLabellingLogic()

    def setup(self):
        """This is called one time when the module GUI is initialized
        """
        ScriptedLoadableModuleWidget.setup(self)

        # Create objects that can be used anywhere in the module. Example: in most cases there should be just one
        # object of the logic class
        self._initLogic_()
        self.currentVolumeLoaded = None
        self.blockNodeEvents = False

        ##########
        # Main area
        self.mainAreaCollapsibleButton = ctk.ctkCollapsibleButton()
        self.mainAreaCollapsibleButton.text = "Main area"
        self.layout.addWidget(self.mainAreaCollapsibleButton,
                              SlicerUtil.ALIGNMENT_VERTICAL_TOP)
        self.mainLayout = qt.QGridLayout(self.mainAreaCollapsibleButton)

        # Node selector
        volumeLabel = qt.QLabel("Active volume: ")
        volumeLabel.setStyleSheet("margin-left:5px")
        self.mainLayout.addWidget(volumeLabel, 0, 0)
        self.volumeSelector = slicer.qMRMLNodeComboBox()
        self.volumeSelector.nodeTypes = ("vtkMRMLScalarVolumeNode", "")
        self.volumeSelector.selectNodeUponCreation = True
        self.volumeSelector.autoFillBackground = True
        self.volumeSelector.addEnabled = False
        self.volumeSelector.noneEnabled = False
        self.volumeSelector.removeEnabled = False
        self.volumeSelector.showHidden = False
        self.volumeSelector.showChildNodeTypes = False
        self.volumeSelector.setMRMLScene(slicer.mrmlScene)
        self.volumeSelector.setFixedWidth(250)
        self.volumeSelector.setStyleSheet("margin: 15px 0")
        #self.volumeSelector.selectNodeUponCreation = False
        self.mainLayout.addWidget(self.volumeSelector, 0, 1, 1, 3)
        self.volumeSelector.connect('currentNodeChanged(vtkMRMLNode*)',
                                    self.__onCurrentNodeChanged__)

        # Radio buttons frame. This will be filled by every child module
        self.radioButtonsFrame = qt.QFrame()
        self.mainLayout.addWidget(self.radioButtonsFrame, 2, 0, 1, 3,
                                  SlicerUtil.ALIGNMENT_VERTICAL_TOP)

        # Load caselist button
        self.loadButton = ctk.ctkPushButton()
        self.loadButton.text = "Load fiducials file"
        self.loadButton.setIcon(
            qt.QIcon("{0}/open_file.png".format(SlicerUtil.CIP_ICON_DIR)))
        self.loadButton.setIconSize(qt.QSize(20, 20))
        self.loadButton.setFixedWidth(135)
        self.mainLayout.addWidget(self.loadButton, 3, 0)
        self.loadButton.connect('clicked()', self.openFiducialsFile)

        # Remove fiducial button
        self.removeLastFiducialButton = ctk.ctkPushButton()
        self.removeLastFiducialButton.text = "Remove last fiducial"
        self.removeLastFiducialButton.toolTip = "Remove the last fiducial added"
        self.removeLastFiducialButton.setIcon(
            qt.QIcon("{0}/delete.png".format(SlicerUtil.CIP_ICON_DIR)))
        self.removeLastFiducialButton.setIconSize(qt.QSize(20, 20))
        self.removeLastFiducialButton.setFixedWidth(200)
        self.mainLayout.addWidget(self.removeLastFiducialButton, 3, 1)
        self.removeLastFiducialButton.connect(
            'clicked()', self.__onRemoveLastFiducialButtonClicked__)

        # Save results button
        self.saveResultsButton = ctk.ctkPushButton()
        self.saveResultsButton.setText("Save markups")
        self.saveResultsButton.toolTip = "Save the markups in the specified directory"
        self.saveResultsButton.setIcon(
            qt.QIcon("{0}/Save.png".format(SlicerUtil.CIP_ICON_DIR)))
        self.saveResultsButton.setIconSize(qt.QSize(20, 20))
        self.saveResultsButton.setFixedWidth(135)
        self.mainLayout.addWidget(self.saveResultsButton, 4, 0)
        self.saveResultsButton.connect('clicked()',
                                       self.__onSaveResultsButtonClicked__)

        # Save results directory button
        defaultPath = os.path.join(
            SlicerUtil.getSettingsDataFolder(self.moduleName),
            "results")  # Assign a default path for the results
        path = SlicerUtil.settingGetOrSetDefault(self.moduleName,
                                                 "SaveResultsDirectory",
                                                 defaultPath)
        self.saveResultsDirectoryButton = ctk.ctkDirectoryButton()
        self.saveResultsDirectoryButton.directory = path
        self.saveResultsDirectoryButton.setMaximumWidth(375)
        self.mainLayout.addWidget(self.saveResultsDirectoryButton, 4, 1, 1, 2)
        self.saveResultsDirectoryButton.connect(
            "directoryChanged (QString)",
            self.__onSaveResultsDirectoryChanged__)

        #####
        # Case navigator
        self.caseNavigatorWidget = None
        if SlicerUtil.isSlicerACILLoaded():
            caseNavigatorAreaCollapsibleButton = ctk.ctkCollapsibleButton()
            caseNavigatorAreaCollapsibleButton.text = "Case navigator"
            self.layout.addWidget(caseNavigatorAreaCollapsibleButton, 0x0020)
            # caseNavigatorLayout = qt.QVBoxLayout(caseNavigatorAreaCollapsibleButton)

            # Add a case list navigator
            from ACIL.ui import CaseNavigatorWidget
            self.caseNavigatorWidget = CaseNavigatorWidget(
                self.moduleName, caseNavigatorAreaCollapsibleButton)
            self.caseNavigatorWidget.setup()
            # Listen for the event of loading a new labelmap
            # self.caseNavigatorWidget.addObservable(self.caseNavigatorWidget.EVENT_LABELMAP_LOADED, self.__onNewILDClassificationLabelmapLoaded__)
            self.caseNavigatorWidget.addObservable(
                self.caseNavigatorWidget.EVENT_BUNDLE_CASE_FINISHED,
                self.__onFinishCaseBundleLoad__)

        self.layout.addStretch()

        # Extra Connections
        self._createSceneObservers_()

    def _createSceneObservers_(self):
        """
        Create the observers for the scene in this module
        """
        self.observers = []
        self.observers.append(
            slicer.mrmlScene.AddObserver(slicer.vtkMRMLScene.NodeAddedEvent,
                                         self.__onNodeAddedObserver__))
        self.observers.append(
            slicer.mrmlScene.AddObserver(slicer.vtkMRMLScene.EndCloseEvent,
                                         self.__onSceneClosed__))

    def saveResultsCurrentNode(self):
        """ Get current active node and save the xml fiducials file
        """
        try:
            d = self.saveResultsDirectoryButton.directory
            if not os.path.isdir(d):
                # Ask the user if he wants to create the folder
                if qt.QMessageBox.question(
                        slicer.util.mainWindow(), "Create directory?",
                        "The directory '{0}' does not exist. Do you want to create it?"
                        .format(d), qt.QMessageBox.Yes
                        | qt.QMessageBox.No) == qt.QMessageBox.Yes:
                    try:
                        os.makedirs(d)
                        # Make sure that everybody has write permissions (sometimes there are problems because of umask)
                        os.chmod(d, 0777)
                    except:
                        qt.QMessageBox.warning(
                            slicer.util.mainWindow(), 'Directory incorrect',
                            'The folder "{0}" could not be created. Please select a valid directory'
                            .format(d))
                        return
                else:
                    # Abort process
                    SlicerUtil.logDevelop("Saving results process aborted",
                                          includePythonConsole=True)
                    return
                    # self.logic.saveCurrentFiducials(d, self.caseNavigatorWidget, self.uploadFileResult)
                    # qt.QMessageBox.information(slicer.util.mainWindow(), 'Results saved',
                    #                            "The results have been saved succesfully")
            # else:
            if SlicerUtil.isSlicerACILLoaded():
                question = qt.QMessageBox.question(
                    slicer.util.mainWindow(), "Save results remotely?",
                    "Your results will be saved locally. Do you also want to save your results in your remote server? (MAD, etc.)",
                    qt.QMessageBox.Yes | qt.QMessageBox.No
                    | qt.QMessageBox.Cancel)
                if question == qt.QMessageBox.Cancel:
                    return
                saveInRemoteRepo = question == qt.QMessageBox.Yes
            else:
                saveInRemoteRepo = False
            self.logic.saveCurrentFiducials(
                d,
                caseNavigatorWidget=self.caseNavigatorWidget,
                callbackFunction=self.uploadFileResult,
                saveInRemoteRepo=saveInRemoteRepo)
            qt.QMessageBox.information(
                slicer.util.mainWindow(), 'Results saved',
                "The results have been saved succesfully")
        except:
            Util.print_last_exception()
            qt.QMessageBox.critical(
                slicer.util.mainWindow(), "Error when saving the results",
                "Error when saving the results. Please review the console for additional info"
            )

    def uploadFileResult(self, result):
        if result != Util.OK:
            qt.QMessageBox.warning(
                slicer.util.mainWindow(), "Error when uploading fiducials",
                "There was an error when uploading the fiducials file. This doesn't mean that your file wasn't saved locally!\n"
                + "Please review the console for more information")

    def openFiducialsFile(self):
        volumeNode = self.volumeSelector.currentNode()
        if volumeNode is None:
            qt.QMessageBox.warning(slicer.util.mainWindow(), 'Select a volume',
                                   'Please load a volume first')
            return

        f = qt.QFileDialog.getOpenFileName()
        if f:
            self.logic.loadFiducialsXml(volumeNode, f)
            self.saveResultsDirectoryButton.directory = os.path.dirname(f)
            qt.QMessageBox.information(slicer.util.mainWindow(), "File loaded",
                                       "File loaded successfully")

    ## PROTECTED/PRIVATE METHODS
    def _checkNewVolume_(self, newVolumeNode):
        """ New volume loaded in the scene in some way.
        If it's really a new volume, try to save and close the current one
        @param newVolumeNode:
        """
        if self.blockNodeEvents:
            # "Semaphore" to avoid duplicated events
            return

        self.blockNodeEvents = True
        volume = self.currentVolumeLoaded
        if volume is not None and newVolumeNode is not None \
                and newVolumeNode.GetID() != volume.GetID()  \
                and not self.logic.isVolumeSaved(volume.GetName()):
            # Ask the user if he wants to save the previously loaded volume
            if qt.QMessageBox.question(
                    slicer.util.mainWindow(), "Save results?",
                    "The fiducials for the volume '{0}' have not been saved. Do you want to save them?"
                    .format(volume.GetName()), qt.QMessageBox.Yes
                    | qt.QMessageBox.No) == qt.QMessageBox.Yes:
                self.saveResultsCurrentNode()
        # Remove all the previously existing nodes
        if self.currentVolumeLoaded is not None and newVolumeNode != self.currentVolumeLoaded:
            # Remove previously existing node
            self.logic.removeMarkupsAndNode(self.currentVolumeLoaded)

        if newVolumeNode is not None:
            SlicerUtil.setActiveVolumeIds(newVolumeNode.GetID())
            SlicerUtil.setFiducialsCursorMode(True, True)

        self.currentVolumeLoaded = newVolumeNode
        self.updateState()
        self.blockNodeEvents = False

    def _getColorTable_(self):
        """ Color table for this module for a better labelmap visualization.
        This must be implemented by child classes"""
        raise NotImplementedError(
            "This method should be implemented by child classes")

    ## EVENTS
    def enter(self):
        """This is invoked every time that we select this module as the active module in Slicer (not only the first time)"""
        self.blockNodeEvents = False
        # if len(self.observers) == 0:
        #     self.observers.append(slicer.mrmlScene.AddObserver(slicer.vtkMRMLScene.NodeAddedEvent, self.__onNodeAddedObserver__))
        #     self.observers.append(slicer.mrmlScene.AddObserver(slicer.vtkMRMLScene.EndCloseEvent, self.__onSceneClosed__))
        SlicerUtil.setFiducialsCursorMode(True, True)

        if self.volumeSelector.currentNodeId != "":
            SlicerUtil.setActiveVolumeIds(self.volumeSelector.currentNodeId)
            self.currentVolumeLoaded = slicer.mrmlScene.GetNodeByID(
                self.volumeSelector.currentNodeId)
            self.updateState()

    def exit(self):
        """This is invoked every time that we switch to another module (not only when Slicer is closed)."""
        try:
            self.blockNodeEvents = True
            SlicerUtil.setFiducialsCursorMode(False)
        except:
            pass

    def cleanup(self):
        """This is invoked as a destructor of the GUI when the module is no longer going to be used"""
        try:
            for observer in self.observers:
                slicer.mrmlScene.RemoveObserver(observer)
                self.observers.remove(observer)
        except:
            pass

    def __onNewVolumeLoaded__(self, newVolumeNode):
        """ Added a new node in the scene
        :param newVolumeNode:
        :return:
        """
        # Filter the name of the volume to remove possible suffixes added by Slicer
        filteredName = SlicerUtil.filterVolumeName(newVolumeNode.GetName())
        newVolumeNode.SetName(filteredName)
        self._checkNewVolume_(newVolumeNode)
        self.blockNodeEvents = True
        self.volumeSelector.setCurrentNode(newVolumeNode)
        self.blockNodeEvents = False

    def __onCurrentNodeChanged__(self, volumeNode):
        self._checkNewVolume_(volumeNode)

    def __onFinishCaseBundleLoad__(self, result, id, ids, additionalFilePaths):
        """
        Event triggered after a volume and the additional files have been loaded for a case.
        In this case, it is important to load a previously existing xml file
        @param result:
        @param id:
        @param ids:
        @param additionalFilePaths:
        @return:
        """
        if result == Util.OK and additionalFilePaths and os.path.exists(
                additionalFilePaths[0]):
            # Try to load a previously existing fiducials file downloaded with the ACIL case navigator
            self.logic.loadFiducialsXml(slicer.util.getNode(id),
                                        additionalFilePaths[0])

    def __onRemoveLastFiducialButtonClicked__(self):
        self.logic.removeLastFiducial()

    def __onSaveResultsDirectoryChanged__(self, directory):
        # f = qt.QFileDialog.getExistingDirectory()
        # if f:
        #     self.saveResultsDirectoryText.setText(f)
        SlicerUtil.setSetting(self.moduleName, "SaveResultsDirectory",
                              directory)

    def __onSaveResultsButtonClicked__(self):
        self.saveResultsCurrentNode()

    def __onSceneClosed__(self, arg1, arg2):
        self.currentVolumeLoaded = None
        self._initLogic_()
Exemple #9
0
    def setup(self):
        """This is called one time when the module GUI is initialized
        """
        ScriptedLoadableModuleWidget.setup(self)

        # Create objects that can be used anywhere in the module. Example: in most cases there should be just one
        # object of the logic class
        self.logic = CIP_PAARatioLogic()

        #
        # Create all the widgets. Example Area
        mainAreaCollapsibleButton = ctk.ctkCollapsibleButton()
        mainAreaCollapsibleButton.text = "Main parameters"
        self.layout.addWidget(mainAreaCollapsibleButton)
        self.mainAreaLayout = qt.QGridLayout(mainAreaCollapsibleButton)

        self.label = qt.QLabel("Select the volume")
        self.label.setStyleSheet("margin:10px 0 20px 7px")
        self.mainAreaLayout.addWidget(self.label, 0, 0)

        self.volumeSelector = slicer.qMRMLNodeComboBox()
        self.volumeSelector.nodeTypes = ("vtkMRMLScalarVolumeNode", "")
        # DEPRECATED. Now there is a new vtkMRMLLabelMapNode
        # self.volumeSelector.addAttribute( "vtkMRMLScalarVolumeNode", "LabelMap", "0" )  # No labelmaps
        self.volumeSelector.selectNodeUponCreation = True
        self.volumeSelector.autoFillBackground = True
        self.volumeSelector.addEnabled = True
        self.volumeSelector.noneEnabled = False
        self.volumeSelector.removeEnabled = False
        self.volumeSelector.showHidden = False
        self.volumeSelector.showChildNodeTypes = False
        self.volumeSelector.setMRMLScene(slicer.mrmlScene)
        self.volumeSelector.setStyleSheet("margin:0px 0 0px 0; padding:2px 0 2px 5px")
        self.mainAreaLayout.addWidget(self.volumeSelector, 0, 1)

        # self.label2 = qt.QLabel("Select the slice")
        # self.label2.setStyleSheet("margin:0px 0 20px 7px; padding-top:20px")
        # self.mainAreaLayout.addWidget(self.label2, 1, 0)

        self.placeDefaultRulersButton = ctk.ctkPushButton()
        self.placeDefaultRulersButton.text = "Place default rulers"
        # self.placeDefaultRulersSliceButton.toolTip = "Navigate to the best estimated slice to place the rulers"
        self.placeDefaultRulersButton.setIcon(qt.QIcon("{0}/next.png".format(SlicerUtil.CIP_ICON_DIR)))
        self.placeDefaultRulersButton.setIconSize(qt.QSize(20, 20))
        self.placeDefaultRulersButton.setStyleSheet("font-weight: bold;")
        # self.placeDefaultRulersButton.setFixedWidth(140)
        self.mainAreaLayout.addWidget(self.placeDefaultRulersButton, 1, 1)

        ### Structure Selector
        self.structuresGroupbox = qt.QGroupBox("Select the structure")
        self.groupboxLayout = qt.QVBoxLayout()
        self.structuresGroupbox.setLayout(self.groupboxLayout)
        self.mainAreaLayout.addWidget(self.structuresGroupbox, 2, 0)

        self.structuresButtonGroup = qt.QButtonGroup()
        # btn = qt.QRadioButton("None")
        # btn.visible = False
        # self.structuresButtonGroup.addButton(btn)
        # self.groupboxLayout.addWidget(btn)

        btn = qt.QRadioButton("Both")
        btn.checked = True
        self.structuresButtonGroup.addButton(btn, 0)
        self.groupboxLayout.addWidget(btn)

        btn = qt.QRadioButton("Pulmonary Arterial")
        self.structuresButtonGroup.addButton(btn, 1)
        self.groupboxLayout.addWidget(btn)

        btn = qt.QRadioButton("Aorta")
        self.structuresButtonGroup.addButton(btn, 2)
        self.groupboxLayout.addWidget(btn)

        ### Buttons toolbox
        self.buttonsToolboxFrame = qt.QFrame()
        self.buttonsToolboxLayout = qt.QGridLayout()
        self.buttonsToolboxFrame.setLayout(self.buttonsToolboxLayout)
        self.mainAreaLayout.addWidget(self.buttonsToolboxFrame, 2, 1)

        self.placeRulersButton = ctk.ctkPushButton()
        self.placeRulersButton.text = "Place ruler/s"
        self.placeRulersButton.toolTip = "Place the ruler/s for the selected structure/s in the current slice"
        self.placeRulersButton.setIcon(qt.QIcon("{0}/ruler.png".format(SlicerUtil.CIP_ICON_DIR)))
        self.placeRulersButton.setIconSize(qt.QSize(20, 20))
        self.placeRulersButton.setFixedWidth(105)
        self.placeRulersButton.setStyleSheet("font-weight:bold")
        self.buttonsToolboxLayout.addWidget(self.placeRulersButton, 0, 0)

        self.moveUpButton = ctk.ctkPushButton()
        self.moveUpButton.text = "Move up"
        self.moveUpButton.toolTip = "Move the selected ruler/s one slice up"
        self.moveUpButton.setIcon(qt.QIcon("{0}/move_up.png".format(SlicerUtil.CIP_ICON_DIR)))
        self.moveUpButton.setIconSize(qt.QSize(20, 20))
        self.moveUpButton.setFixedWidth(95)
        self.buttonsToolboxLayout.addWidget(self.moveUpButton, 0, 1)

        self.moveDownButton = ctk.ctkPushButton()
        self.moveDownButton.text = "Move down"
        self.moveDownButton.toolTip = "Move the selected ruler/s one slice down"
        self.moveDownButton.setIcon(qt.QIcon("{0}/move_down.png".format(SlicerUtil.CIP_ICON_DIR)))
        self.moveDownButton.setIconSize(qt.QSize(20, 20))
        self.moveDownButton.setFixedWidth(95)
        self.buttonsToolboxLayout.addWidget(self.moveDownButton, 0, 2)

        self.removeButton = ctk.ctkPushButton()
        self.removeButton.text = "Remove ALL rulers"
        self.removeButton.toolTip = "Remove all the rulers for this volume"
        self.removeButton.setIcon(qt.QIcon("{0}/delete.png".format(SlicerUtil.CIP_ICON_DIR)))
        self.removeButton.setIconSize(qt.QSize(20, 20))
        self.buttonsToolboxLayout.addWidget(self.removeButton, 1, 1, 1, 2, 2)

        ### Textboxes
        self.textboxesFrame = qt.QFrame()
        self.textboxesLayout = qt.QFormLayout()
        self.textboxesFrame.setLayout(self.textboxesLayout)
        self.textboxesFrame.setFixedWidth(190)
        self.mainAreaLayout.addWidget(self.textboxesFrame, 3, 0)

        self.paTextBox = qt.QLineEdit()
        self.paTextBox.setReadOnly(True)
        self.textboxesLayout.addRow("PA (mm):  ", self.paTextBox)

        self.aortaTextBox = qt.QLineEdit()
        self.aortaTextBox.setReadOnly(True)
        self.textboxesLayout.addRow("Aorta (mm):  ", self.aortaTextBox)

        self.ratioTextBox = qt.QLineEdit()
        self.ratioTextBox.setReadOnly(True)
        self.textboxesLayout.addRow("Ratio PA/A: ", self.ratioTextBox)

        # Save case data
        self.reportsCollapsibleButton = ctk.ctkCollapsibleButton()
        self.reportsCollapsibleButton.text = "Reporting"
        self.layout.addWidget(self.reportsCollapsibleButton)
        self.reportsLayout = qt.QHBoxLayout(self.reportsCollapsibleButton)

        self.storedColumnNames = [
            "caseId",
            "paDiameter_mm",
            "aortaDiameter_mm",
            "pa1r",
            "pa1a",
            "pa1s",
            "pa2r",
            "pa2a",
            "pa2s",
            "a1r",
            "a1a",
            "a1s",
            "a2r",
            "a2a",
            "a2s",
        ]
        self.reportsWidget = CaseReportsWidget(
            "CIP_PAARatio", columnNames=self.storedColumnNames, parentWidget=self.reportsCollapsibleButton
        )
        self.reportsWidget.setup()

        self.switchToRedView()

        #####
        # Case navigator
        if SlicerUtil.isSlicerACILLoaded():
            caseNavigatorAreaCollapsibleButton = ctk.ctkCollapsibleButton()
            caseNavigatorAreaCollapsibleButton.text = "Case navigator"
            self.layout.addWidget(caseNavigatorAreaCollapsibleButton, 0x0020)
            # caseNavigatorLayout = qt.QVBoxLayout(caseNavigatorAreaCollapsibleButton)

            # Add a case list navigator
            from ACIL.ui import CaseNavigatorWidget

            self.caseNavigatorWidget = CaseNavigatorWidget(self.moduleName, caseNavigatorAreaCollapsibleButton)
            self.caseNavigatorWidget.setup()

        self.layout.addStretch()

        # Connections
        self.observers = []
        self.__addSceneObservables__()

        self.volumeSelector.connect("currentNodeChanged(vtkMRMLNode*)", self.onVolumeSelectorChanged)
        self.placeDefaultRulersButton.connect("clicked()", self.oPlaceDefaultRulersClicked)
        self.placeRulersButton.connect("clicked()", self.onPlaceRulersClicked)
        self.moveUpButton.connect("clicked()", self.onMoveUpRulerClicked)
        self.moveDownButton.connect("clicked()", self.onMoveDownRulerClicked)
        self.removeButton.connect("clicked()", self.onRemoveRulerClicked)

        self.reportsWidget.addObservable(self.reportsWidget.EVENT_SAVE_BUTTON_CLICKED, self.onSaveReport)
Exemple #10
0
class CIP_PAARatioWidget(ScriptedLoadableModuleWidget):
    """Uses ScriptedLoadableModuleWidget base class, available at:
    https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
    """

    @property
    def moduleName(self):
        return "CIP_PAARatio"

    def __init__(self, parent):
        ScriptedLoadableModuleWidget.__init__(self, parent)

        from functools import partial

        def __onNodeAddedObserver__(self, caller, eventId, callData):
            """Node added to the Slicer scene"""
            if (
                callData.GetClassName() == "vtkMRMLScalarVolumeNode"
                and slicer.util.mainWindow().moduleSelector().selectedModule == self.moduleName
            ):  # Current module visible
                self.volumeSelector.setCurrentNode(callData)

        self.__onNodeAddedObserver__ = partial(__onNodeAddedObserver__, self)
        self.__onNodeAddedObserver__.CallDataType = vtk.VTK_OBJECT

    def setup(self):
        """This is called one time when the module GUI is initialized
        """
        ScriptedLoadableModuleWidget.setup(self)

        # Create objects that can be used anywhere in the module. Example: in most cases there should be just one
        # object of the logic class
        self.logic = CIP_PAARatioLogic()

        #
        # Create all the widgets. Example Area
        mainAreaCollapsibleButton = ctk.ctkCollapsibleButton()
        mainAreaCollapsibleButton.text = "Main parameters"
        self.layout.addWidget(mainAreaCollapsibleButton)
        self.mainAreaLayout = qt.QGridLayout(mainAreaCollapsibleButton)

        self.label = qt.QLabel("Select the volume")
        self.label.setStyleSheet("margin:10px 0 20px 7px")
        self.mainAreaLayout.addWidget(self.label, 0, 0)

        self.volumeSelector = slicer.qMRMLNodeComboBox()
        self.volumeSelector.nodeTypes = ("vtkMRMLScalarVolumeNode", "")
        # DEPRECATED. Now there is a new vtkMRMLLabelMapNode
        # self.volumeSelector.addAttribute( "vtkMRMLScalarVolumeNode", "LabelMap", "0" )  # No labelmaps
        self.volumeSelector.selectNodeUponCreation = True
        self.volumeSelector.autoFillBackground = True
        self.volumeSelector.addEnabled = True
        self.volumeSelector.noneEnabled = False
        self.volumeSelector.removeEnabled = False
        self.volumeSelector.showHidden = False
        self.volumeSelector.showChildNodeTypes = False
        self.volumeSelector.setMRMLScene(slicer.mrmlScene)
        self.volumeSelector.setStyleSheet("margin:0px 0 0px 0; padding:2px 0 2px 5px")
        self.mainAreaLayout.addWidget(self.volumeSelector, 0, 1)

        # self.label2 = qt.QLabel("Select the slice")
        # self.label2.setStyleSheet("margin:0px 0 20px 7px; padding-top:20px")
        # self.mainAreaLayout.addWidget(self.label2, 1, 0)

        self.placeDefaultRulersButton = ctk.ctkPushButton()
        self.placeDefaultRulersButton.text = "Place default rulers"
        # self.placeDefaultRulersSliceButton.toolTip = "Navigate to the best estimated slice to place the rulers"
        self.placeDefaultRulersButton.setIcon(qt.QIcon("{0}/next.png".format(SlicerUtil.CIP_ICON_DIR)))
        self.placeDefaultRulersButton.setIconSize(qt.QSize(20, 20))
        self.placeDefaultRulersButton.setStyleSheet("font-weight: bold;")
        # self.placeDefaultRulersButton.setFixedWidth(140)
        self.mainAreaLayout.addWidget(self.placeDefaultRulersButton, 1, 1)

        ### Structure Selector
        self.structuresGroupbox = qt.QGroupBox("Select the structure")
        self.groupboxLayout = qt.QVBoxLayout()
        self.structuresGroupbox.setLayout(self.groupboxLayout)
        self.mainAreaLayout.addWidget(self.structuresGroupbox, 2, 0)

        self.structuresButtonGroup = qt.QButtonGroup()
        # btn = qt.QRadioButton("None")
        # btn.visible = False
        # self.structuresButtonGroup.addButton(btn)
        # self.groupboxLayout.addWidget(btn)

        btn = qt.QRadioButton("Both")
        btn.checked = True
        self.structuresButtonGroup.addButton(btn, 0)
        self.groupboxLayout.addWidget(btn)

        btn = qt.QRadioButton("Pulmonary Arterial")
        self.structuresButtonGroup.addButton(btn, 1)
        self.groupboxLayout.addWidget(btn)

        btn = qt.QRadioButton("Aorta")
        self.structuresButtonGroup.addButton(btn, 2)
        self.groupboxLayout.addWidget(btn)

        ### Buttons toolbox
        self.buttonsToolboxFrame = qt.QFrame()
        self.buttonsToolboxLayout = qt.QGridLayout()
        self.buttonsToolboxFrame.setLayout(self.buttonsToolboxLayout)
        self.mainAreaLayout.addWidget(self.buttonsToolboxFrame, 2, 1)

        self.placeRulersButton = ctk.ctkPushButton()
        self.placeRulersButton.text = "Place ruler/s"
        self.placeRulersButton.toolTip = "Place the ruler/s for the selected structure/s in the current slice"
        self.placeRulersButton.setIcon(qt.QIcon("{0}/ruler.png".format(SlicerUtil.CIP_ICON_DIR)))
        self.placeRulersButton.setIconSize(qt.QSize(20, 20))
        self.placeRulersButton.setFixedWidth(105)
        self.placeRulersButton.setStyleSheet("font-weight:bold")
        self.buttonsToolboxLayout.addWidget(self.placeRulersButton, 0, 0)

        self.moveUpButton = ctk.ctkPushButton()
        self.moveUpButton.text = "Move up"
        self.moveUpButton.toolTip = "Move the selected ruler/s one slice up"
        self.moveUpButton.setIcon(qt.QIcon("{0}/move_up.png".format(SlicerUtil.CIP_ICON_DIR)))
        self.moveUpButton.setIconSize(qt.QSize(20, 20))
        self.moveUpButton.setFixedWidth(95)
        self.buttonsToolboxLayout.addWidget(self.moveUpButton, 0, 1)

        self.moveDownButton = ctk.ctkPushButton()
        self.moveDownButton.text = "Move down"
        self.moveDownButton.toolTip = "Move the selected ruler/s one slice down"
        self.moveDownButton.setIcon(qt.QIcon("{0}/move_down.png".format(SlicerUtil.CIP_ICON_DIR)))
        self.moveDownButton.setIconSize(qt.QSize(20, 20))
        self.moveDownButton.setFixedWidth(95)
        self.buttonsToolboxLayout.addWidget(self.moveDownButton, 0, 2)

        self.removeButton = ctk.ctkPushButton()
        self.removeButton.text = "Remove ALL rulers"
        self.removeButton.toolTip = "Remove all the rulers for this volume"
        self.removeButton.setIcon(qt.QIcon("{0}/delete.png".format(SlicerUtil.CIP_ICON_DIR)))
        self.removeButton.setIconSize(qt.QSize(20, 20))
        self.buttonsToolboxLayout.addWidget(self.removeButton, 1, 1, 1, 2, 2)

        ### Textboxes
        self.textboxesFrame = qt.QFrame()
        self.textboxesLayout = qt.QFormLayout()
        self.textboxesFrame.setLayout(self.textboxesLayout)
        self.textboxesFrame.setFixedWidth(190)
        self.mainAreaLayout.addWidget(self.textboxesFrame, 3, 0)

        self.paTextBox = qt.QLineEdit()
        self.paTextBox.setReadOnly(True)
        self.textboxesLayout.addRow("PA (mm):  ", self.paTextBox)

        self.aortaTextBox = qt.QLineEdit()
        self.aortaTextBox.setReadOnly(True)
        self.textboxesLayout.addRow("Aorta (mm):  ", self.aortaTextBox)

        self.ratioTextBox = qt.QLineEdit()
        self.ratioTextBox.setReadOnly(True)
        self.textboxesLayout.addRow("Ratio PA/A: ", self.ratioTextBox)

        # Save case data
        self.reportsCollapsibleButton = ctk.ctkCollapsibleButton()
        self.reportsCollapsibleButton.text = "Reporting"
        self.layout.addWidget(self.reportsCollapsibleButton)
        self.reportsLayout = qt.QHBoxLayout(self.reportsCollapsibleButton)

        self.storedColumnNames = [
            "caseId",
            "paDiameter_mm",
            "aortaDiameter_mm",
            "pa1r",
            "pa1a",
            "pa1s",
            "pa2r",
            "pa2a",
            "pa2s",
            "a1r",
            "a1a",
            "a1s",
            "a2r",
            "a2a",
            "a2s",
        ]
        self.reportsWidget = CaseReportsWidget(
            "CIP_PAARatio", columnNames=self.storedColumnNames, parentWidget=self.reportsCollapsibleButton
        )
        self.reportsWidget.setup()

        self.switchToRedView()

        #####
        # Case navigator
        if SlicerUtil.isSlicerACILLoaded():
            caseNavigatorAreaCollapsibleButton = ctk.ctkCollapsibleButton()
            caseNavigatorAreaCollapsibleButton.text = "Case navigator"
            self.layout.addWidget(caseNavigatorAreaCollapsibleButton, 0x0020)
            # caseNavigatorLayout = qt.QVBoxLayout(caseNavigatorAreaCollapsibleButton)

            # Add a case list navigator
            from ACIL.ui import CaseNavigatorWidget

            self.caseNavigatorWidget = CaseNavigatorWidget(self.moduleName, caseNavigatorAreaCollapsibleButton)
            self.caseNavigatorWidget.setup()

        self.layout.addStretch()

        # Connections
        self.observers = []
        self.__addSceneObservables__()

        self.volumeSelector.connect("currentNodeChanged(vtkMRMLNode*)", self.onVolumeSelectorChanged)
        self.placeDefaultRulersButton.connect("clicked()", self.oPlaceDefaultRulersClicked)
        self.placeRulersButton.connect("clicked()", self.onPlaceRulersClicked)
        self.moveUpButton.connect("clicked()", self.onMoveUpRulerClicked)
        self.moveDownButton.connect("clicked()", self.onMoveDownRulerClicked)
        self.removeButton.connect("clicked()", self.onRemoveRulerClicked)

        self.reportsWidget.addObservable(self.reportsWidget.EVENT_SAVE_BUTTON_CLICKED, self.onSaveReport)

    def enter(self):
        """This is invoked every time that we select this module as the active module in Slicer (not only the first time)"""
        # activeVolumeId = SlicerUtil.getActiveVolumeIdInRedSlice()
        # if activeVolumeId is not None:
        #     self.volumeSelector.setCurrentNodeID(activeVolumeId)
        #     if activeVolumeId not in self.logic.currentVolumesLoaded:
        #         self.placeDefaultRulers(activeVolumeId)
        volumeId = self.volumeSelector.currentNodeId
        if volumeId:
            SlicerUtil.setActiveVolumeId(volumeId)

    def exit(self):
        """This is invoked every time that we switch to another module (not only when Slicer is closed)."""
        pass

    def cleanup(self):
        """This is invoked as a destructor of the GUI when the module is no longer going to be used"""
        pass

    def jumpToTemptativeSlice(self, volumeId):
        """ Jump the red window to a predefined slice based on the size of the volume
        :param volumeId:
        """
        # Get the default coordinates of the ruler
        aorta1, aorta2, pa1, pa2 = self.logic.getDefaultCoords(volumeId)
        # Set the display in the right slice
        self.moveRedWindowToSlice(aorta1[2])

    def placeDefaultRulers(self, volumeId):
        """ Set the Aorta and PA rulers to a default estimated position and jump to that slice
        :param volumeId:
        """
        if not volumeId:
            return
        # Hide all the actual ruler nodes
        self.logic.hideAllRulers()
        # Remove the current rulers for this volume
        self.logic.removeRulers(volumeId)
        # Create the default rulers
        self.logic.createDefaultRulers(volumeId, self.onRulerUpdated)
        # Activate both structures
        self.structuresButtonGroup.buttons()[0].setChecked(True)
        # Jump to the slice where the rulers are
        self.jumpToTemptativeSlice(volumeId)
        # Place the rulers in the current slice
        self.placeRuler()
        # Add the current volume to the list of loaded volumes
        self.logic.currentVolumesLoaded.add(volumeId)

        # Modify the zoom of the Red slice
        redSliceNode = slicer.util.getFirstNodeByClassByName("vtkMRMLSliceNode", "Red")
        factor = 0.5
        newFOVx = redSliceNode.GetFieldOfView()[0] * factor
        newFOVy = redSliceNode.GetFieldOfView()[1] * factor
        newFOVz = redSliceNode.GetFieldOfView()[2]
        redSliceNode.SetFieldOfView(newFOVx, newFOVy, newFOVz)
        # Move the camera up to fix the view
        redSliceNode.SetXYZOrigin(0, 50, 0)
        # Refresh the data in the viewer
        redSliceNode.UpdateMatrices()

    def placeRuler(self):
        """ Place one or the two rulers in the current visible slice in Red node
        """
        volumeId = self.volumeSelector.currentNodeId
        if volumeId == "":
            self.showUnselectedVolumeWarningMessage()
            return

        selectedStructure = self.getCurrentSelectedStructure()
        if selectedStructure == self.logic.NONE:
            qt.QMessageBox.warning(
                slicer.util.mainWindow(),
                "Review structure",
                "Please select Pulmonary Arterial, Aorta or both to place the right ruler/s",
            )
            return

        # Get the current slice
        currentSlice = self.getCurrentRedWindowSlice()

        if selectedStructure == self.logic.BOTH:
            structures = [self.logic.PA, self.logic.AORTA]
        else:
            structures = [selectedStructure]

        for structure in structures:
            self.logic.placeRulerInSlice(volumeId, structure, currentSlice, self.onRulerUpdated)

        self.refreshTextboxes()

    def getCurrentSelectedStructure(self):
        """ Get the current selected structure id
        :return: self.logic.AORTA or self.logic.PA
        """
        selectedStructureText = self.structuresButtonGroup.checkedButton().text
        if selectedStructureText == "Aorta":
            return self.logic.AORTA
        elif selectedStructureText == "Pulmonary Arterial":
            return self.logic.PA
        elif selectedStructureText == "Both":
            return self.logic.BOTH
        return self.logic.NONE

    def stepSlice(self, offset):
        """ Move the selected structure one slice up or down
        :param offset: +1 or -1
        :return:
        """
        volumeId = self.volumeSelector.currentNodeId

        if volumeId == "":
            self.showUnselectedVolumeWarningMessage()
            return

        selectedStructure = self.getCurrentSelectedStructure()
        if selectedStructure == self.logic.NONE:
            self.showUnselectedStructureWarningMessage()
            return

        if selectedStructure == self.logic.BOTH:
            # Move both rulers
            self.logic.stepSlice(volumeId, self.logic.AORTA, offset)
            newSlice = self.logic.stepSlice(volumeId, self.logic.PA, offset)
        else:
            newSlice = self.logic.stepSlice(volumeId, selectedStructure, offset)

        self.moveRedWindowToSlice(newSlice)

    def removeRulers(self):
        """ Remove all the rulers related to the current volume node
        :return:
        """
        self.logic.removeRulers(self.volumeSelector.currentNodeId)
        self.refreshTextboxes(reset=True)

    def getCurrentRedWindowSlice(self):
        """ Get the current slice (in RAS) of the Red window
        :return:
        """
        redNodeSliceNode = slicer.app.layoutManager().sliceWidget("Red").sliceLogic().GetSliceNode()
        return redNodeSliceNode.GetSliceOffset()

    def moveRedWindowToSlice(self, newSlice):
        """ Moves the red display to the specified RAS slice
        :param newSlice: slice to jump (RAS format)
        :return:
        """
        redNodeSliceNode = slicer.app.layoutManager().sliceWidget("Red").sliceLogic().GetSliceNode()
        redNodeSliceNode.JumpSlice(0, 0, newSlice)

    def refreshTextboxes(self, reset=False):
        """ Update the information of the textboxes that give information about the measurements
        """
        self.aortaTextBox.setText("0")
        self.paTextBox.setText("0")
        self.ratioTextBox.setText("0")
        self.ratioTextBox.setStyleSheet(" QLineEdit { background-color: white; color: black}")

        volumeId = self.volumeSelector.currentNodeId
        if volumeId not in self.logic.currentVolumesLoaded:
            return

        if volumeId:
            self.logic.changeColor(volumeId, self.logic.defaultColor)
        aorta = None
        pa = None
        if not reset:
            rulerAorta, newAorta = self.logic.getRulerNodeForVolumeAndStructure(
                self.volumeSelector.currentNodeId, self.logic.AORTA, createIfNotExist=False
            )
            rulerPA, newPA = self.logic.getRulerNodeForVolumeAndStructure(
                self.volumeSelector.currentNodeId, self.logic.PA, createIfNotExist=False
            )
            if rulerAorta:
                aorta = rulerAorta.GetDistanceMeasurement()
                self.aortaTextBox.setText(str(aorta))
            if rulerPA:
                pa = rulerPA.GetDistanceMeasurement()
                self.paTextBox.setText(str(pa))
            if aorta is not None and aorta != 0:
                try:
                    ratio = pa / aorta
                    self.ratioTextBox.setText(str(ratio))
                    if ratio > 1:
                        # Switch colors ("alarm")
                        self.ratioTextBox.setStyleSheet(" QLineEdit { background-color: rgb(255, 0, 0); color: white}")
                        self.logic.changeColor(volumeId, self.logic.defaultWarningColor)
                except Exception:
                    Util.print_last_exception()

    def showUnselectedVolumeWarningMessage(self):
        qt.QMessageBox.warning(slicer.util.mainWindow(), "Select a volume", "Please select a volume")

    def showUnselectedStructureWarningMessage(self):
        qt.QMessageBox.warning(
            slicer.util.mainWindow(),
            "Review structure",
            "Please select Aorta, Pulmonary Arterial or Both to place the right ruler/s",
        )

    def switchToRedView(self):
        """ Switch the layout to Red slice only
        :return:
        """
        layoutManager = slicer.app.layoutManager()
        layoutManager.setLayout(6)

    def __addSceneObservables__(self):
        self.observers.append(
            slicer.mrmlScene.AddObserver(slicer.vtkMRMLScene.NodeAddedEvent, self.__onNodeAddedObserver__)
        )
        self.observers.append(slicer.mrmlScene.AddObserver(slicer.vtkMRMLScene.EndCloseEvent, self.__onSceneClosed__))

    def __removeSceneObservables(self):
        for observer in self.observers:
            slicer.mrmlScene.RemoveObserver(observer)
            self.observers.remove(observer)

    #########
    # EVENTS
    def onVolumeSelectorChanged(self, node):
        # if node is not None and node.GetID() not in self.currentVolumesLoaded:
        # if node is not None:
        #     # New node. Load default rulers
        #     if node.GetID() not in self.logic.currentVolumesLoaded:
        #         self.placeDefaultRulers(node.GetID())
        self.refreshTextboxes()

    def onStructureClicked(self, button):
        fiducialsNode = self.getFiducialsNode(self.volumeSelector.currentNodeId)
        if fiducialsNode is not None:
            self.__addRuler__(button.text, self.volumeSelector.currentNodeId)

            markupsLogic = slicer.modules.markups.logic()
            markupsLogic.SetActiveListID(fiducialsNode)

            applicationLogic = slicer.app.applicationLogic()
            selectionNode = applicationLogic.GetSelectionNode()

            selectionNode.SetReferenceActivePlaceNodeClassName("vtkMRMLAnnotationRulerNode")
            interactionNode = applicationLogic.GetInteractionNode()
            interactionNode.SwitchToSinglePlaceMode()

    def oPlaceDefaultRulersClicked(self):
        volumeId = self.volumeSelector.currentNodeId
        if volumeId == "":
            self.showUnselectedVolumeWarningMessage()
            return
        self.placeDefaultRulers(volumeId)

    def onRulerUpdated(self, node, event):
        self.refreshTextboxes()

    def onPlaceRulersClicked(self):
        self.placeRuler()

    def onMoveUpRulerClicked(self):
        self.stepSlice(1)

    def onMoveDownRulerClicked(self):
        self.stepSlice(-1)

    def onRemoveRulerClicked(self):
        if (
            qt.QMessageBox.question(
                slicer.util.mainWindow(),
                "Remove rulers",
                "Are you sure you want to remove all the rulers from this volume?",
                qt.QMessageBox.Yes | qt.QMessageBox.No,
            )
        ) == qt.QMessageBox.Yes:
            self.logic.removeRulers(self.volumeSelector.currentNodeId)
            self.refreshTextboxes()

    def onSaveReport(self):
        """ Save the current values in a persistent csv file
        :return:
        """
        volumeId = self.volumeSelector.currentNodeId
        if volumeId:
            caseName = slicer.mrmlScene.GetNodeByID(volumeId).GetName()
            coords = [0, 0, 0, 0]
            pa1 = pa2 = a1 = a2 = None
            # PA
            rulerNode, newNode = self.logic.getRulerNodeForVolumeAndStructure(
                volumeId, self.logic.PA, createIfNotExist=False
            )
            if rulerNode:
                # Get current RAS coords
                rulerNode.GetPositionWorldCoordinates1(coords)
                pa1 = list(coords)
                rulerNode.GetPositionWorldCoordinates2(coords)
                pa2 = list(coords)
            # AORTA
            rulerNode, newNode = self.logic.getRulerNodeForVolumeAndStructure(
                volumeId, self.logic.AORTA, createIfNotExist=False
            )
            if rulerNode:
                rulerNode.GetPositionWorldCoordinates1(coords)
                a1 = list(coords)
                rulerNode.GetPositionWorldCoordinates2(coords)
                a2 = list(coords)
            self.reportsWidget.saveCurrentValues(
                caseId=caseName,
                paDiameter_mm=self.paTextBox.text,
                aortaDiameter_mm=self.aortaTextBox.text,
                pa1r=pa1[0] if pa1 is not None else "",
                pa1a=pa1[1] if pa1 is not None else "",
                pa1s=pa1[2] if pa1 is not None else "",
                pa2r=pa2[0] if pa2 is not None else "",
                pa2a=pa2[1] if pa2 is not None else "",
                pa2s=pa2[2] if pa2 is not None else "",
                a1r=a1[0] if a1 is not None else "",
                a1a=a1[1] if a1 is not None else "",
                a1s=a1[2] if a1 is not None else "",
                a2r=a2[0] if a2 is not None else "",
                a2a=a2[1] if a2 is not None else "",
                a2s=a2[2] if a2 is not None else "",
            )
            qt.QMessageBox.information(slicer.util.mainWindow(), "Data saved", "The data were saved successfully")

    def __onSceneClosed__(self, arg1, arg2):
        """ Scene closed. Reset currently loaded volumes
        :param arg1:
        :param arg2:
        :return:
        """
        self.logic.currentVolumesLoaded.clear()
    def setup(self):
        """This is called one time when the module GUI is initialized
        """
        ScriptedLoadableModuleWidget.setup(self)

        # Create objects that can be used anywhere in the module. Example: in most cases there should be just one
        # object of the logic class
        self.logic = CIP_ParenchymaSubtypeTrainingLogic()
        self.currentVolumeLoaded = None
        self.blockNodeEvents = False

        ##########
        # Main area
        self.mainAreaCollapsibleButton = ctk.ctkCollapsibleButton()
        self.mainAreaCollapsibleButton.text = "Main area"
        self.layout.addWidget(self.mainAreaCollapsibleButton, SlicerUtil.ALIGNMENT_VERTICAL_TOP)
        self.mainLayout = qt.QGridLayout(self.mainAreaCollapsibleButton)

        # Node selector
        volumeLabel = qt.QLabel("Active volume: ")
        volumeLabel.setStyleSheet("margin-left:5px")
        self.mainLayout.addWidget(volumeLabel, 0, 0)
        self.volumeSelector = slicer.qMRMLNodeComboBox()
        self.volumeSelector.nodeTypes = ("vtkMRMLScalarVolumeNode", "")
        self.volumeSelector.selectNodeUponCreation = True
        self.volumeSelector.autoFillBackground = True
        self.volumeSelector.addEnabled = False
        self.volumeSelector.noneEnabled = False
        self.volumeSelector.removeEnabled = False
        self.volumeSelector.showHidden = False
        self.volumeSelector.showChildNodeTypes = False
        self.volumeSelector.setMRMLScene(slicer.mrmlScene)
        self.volumeSelector.setFixedWidth(250)
        self.volumeSelector.setStyleSheet("margin: 15px 0")
        #self.volumeSelector.selectNodeUponCreation = False
        self.mainLayout.addWidget(self.volumeSelector, 0, 1, 1, 3)

        labelsStyle = "font-weight: bold; margin: 0 0 5px 5px;"
        # Types Radio Buttons
        typesLabel = qt.QLabel("Select the type")
        typesLabel.setStyleSheet(labelsStyle)
        typesLabel.setFixedHeight(15)
        self.mainLayout.addWidget(typesLabel, 1, 0)
        self.typesFrame = qt.QFrame()
        self.typesLayout = qt.QVBoxLayout(self.typesFrame)
        self.mainLayout.addWidget(self.typesFrame, 2, 0, SlicerUtil.ALIGNMENT_VERTICAL_TOP)
        self.typesRadioButtonGroup = qt.QButtonGroup()
        for key in self.logic.params.mainTypes.iterkeys():
            rbitem = qt.QRadioButton(self.logic.params.getMainTypeLabel(key))
            self.typesRadioButtonGroup.addButton(rbitem, key)
            self.typesLayout.addWidget(rbitem)
        self.typesRadioButtonGroup.buttons()[0].setChecked(True)

        # Subtypes Radio buttons
        subtypesLabel = qt.QLabel("Select the subtype")
        subtypesLabel.setStyleSheet(labelsStyle)
        subtypesLabel.setFixedHeight(15)
        self.mainLayout.addWidget(subtypesLabel, 1, 1)
        self.subtypesRadioButtonGroup = qt.QButtonGroup()
        self.subtypesFrame = qt.QFrame()
        self.subtypesFrame.setMinimumHeight(275)
        self.subtypesLayout = qt.QVBoxLayout(self.subtypesFrame)
        self.subtypesLayout.setAlignment(SlicerUtil.ALIGNMENT_VERTICAL_TOP)
        self.subtypesLayout.setStretch(0, 0)
        self.mainLayout.addWidget(self.subtypesFrame, 2, 1, SlicerUtil.ALIGNMENT_VERTICAL_TOP)     # Put the frame in the top
        # The content will be loaded dynamically every time the main type is modified

        # Artifact radio buttons
        self.artifactsLabel = qt.QLabel("Artifact")
        self.artifactsLabel.setStyleSheet(labelsStyle)
        self.artifactsLabel.setFixedHeight(15)
        self.mainLayout.addWidget(self.artifactsLabel, 1, 2)
        #self.mainLayout.addWidget(qt.QLabel("Select the artifact"), 1, 1)
        self.artifactsRadioButtonGroup = qt.QButtonGroup()
        self.artifactsFrame = qt.QFrame()
        self.artifactsLayout = qt.QVBoxLayout(self.artifactsFrame)
        self.mainLayout.addWidget(self.artifactsFrame, 2, 2, SlicerUtil.ALIGNMENT_VERTICAL_TOP)
        self.artifactsRadioButtonGroup = qt.QButtonGroup()
        for artifactId in self.logic.params.artifacts.iterkeys():
            rbitem = qt.QRadioButton(self.logic.params.getArtifactLabel(artifactId))
            self.artifactsRadioButtonGroup.addButton(rbitem, artifactId)
            self.artifactsLayout.addWidget(rbitem)
        self.artifactsRadioButtonGroup.buttons()[0].setChecked(True)

        # Load caselist button
        self.loadButton = ctk.ctkPushButton()
        self.loadButton.text = "Load fiducials file"
        self.loadButton.setIcon(qt.QIcon("{0}/open_file.png".format(SlicerUtil.CIP_ICON_DIR)))
        self.loadButton.setIconSize(qt.QSize(20, 20))
        self.loadButton.setFixedWidth(135)
        self.mainLayout.addWidget(self.loadButton, 3, 0)

        # Remove fiducial button
        self.removeLastFiducialButton = ctk.ctkPushButton()
        self.removeLastFiducialButton.text = "Remove last fiducial"
        self.removeLastFiducialButton.toolTip = "Remove the last fiducial added"
        self.removeLastFiducialButton.setIcon(qt.QIcon("{0}/delete.png".format(SlicerUtil.CIP_ICON_DIR)))
        self.removeLastFiducialButton.setIconSize(qt.QSize(20, 20))
        self.removeLastFiducialButton.setFixedWidth(200)
        self.mainLayout.addWidget(self.removeLastFiducialButton, 3, 1)

        # Save results button
        self.saveResultsButton = ctk.ctkPushButton()
        self.saveResultsButton.setText("Save markups")
        self.saveResultsButton.toolTip = "Save the markups in the specified directory"
        self.saveResultsButton.setIcon(qt.QIcon("{0}/Save.png".format(SlicerUtil.CIP_ICON_DIR)))
        self.saveResultsButton.setIconSize(qt.QSize(20,20))
        self.mainLayout.addWidget(self.saveResultsButton, 4, 0)

        # Save results directory button
        defaultPath = os.path.join(SlicerUtil.getSettingsDataFolder(self.moduleName), "Results")     # Assign a default path for the results
        path = SlicerUtil.settingGetOrSetDefault(self.moduleName, "SaveResultsDirectory", defaultPath)
        self.saveResultsDirectoryButton = ctk.ctkDirectoryButton()
        self.saveResultsDirectoryButton.directory = path
        self.saveResultsDirectoryButton.setMaximumWidth(375)
        self.mainLayout.addWidget(self.saveResultsDirectoryButton, 4, 1, 1, 2)

        #####
        # Case navigator
        if SlicerUtil.isSlicerACILLoaded():
            caseNavigatorAreaCollapsibleButton = ctk.ctkCollapsibleButton()
            caseNavigatorAreaCollapsibleButton.text = "Case navigator"
            self.layout.addWidget(caseNavigatorAreaCollapsibleButton, 0x0020)
            # caseNavigatorLayout = qt.QVBoxLayout(caseNavigatorAreaCollapsibleButton)

            # Add a case list navigator
            from ACIL.ui import CaseNavigatorWidget
            self.caseNavigatorWidget = CaseNavigatorWidget(self.moduleName, caseNavigatorAreaCollapsibleButton)
            self.caseNavigatorWidget.setup()
            # Listen for the event of loading volume
            #self.caseNavigatorWidget.addObservable(self.caseNavigatorWidget.EVENT_VOLUME_LOADED, self.onNewVolumeLoaded)

        self.layout.addStretch()

        self.updateState()

        # Connections
        self.typesRadioButtonGroup.connect("buttonClicked (QAbstractButton*)", self.__onTypesRadioButtonClicked__)
        self.subtypesRadioButtonGroup.connect("buttonClicked (QAbstractButton*)", self.__onSubtypesRadioButtonClicked__)
        self.artifactsRadioButtonGroup.connect("buttonClicked (QAbstractButton*)", self.__onSubtypesRadioButtonClicked__)

        self.volumeSelector.connect('currentNodeChanged(vtkMRMLNode*)', self.__onCurrentNodeChanged__)
        self.loadButton.connect('clicked()', self.openFiducialsFile)
        self.removeLastFiducialButton.connect('clicked()', self.__onRemoveLastFiducialButtonClicked__)
        # self.saveResultsOpenDirectoryDialogButton.connect('clicked()', self.onOpenDirectoryDialogButtonClicked)
        self.saveResultsDirectoryButton.connect("directoryChanged (str)", self.__onSaveResultsDirectoryChanged__)
        self.saveResultsButton.connect('clicked()', self.__onSaveResultsButtonClicked__)

        self.observers = []
        self.observers.append(slicer.mrmlScene.AddObserver(slicer.vtkMRMLScene.NodeAddedEvent, self.__onNodeAddedObserver__))
        self.observers.append(slicer.mrmlScene.AddObserver(slicer.vtkMRMLScene.EndCloseEvent, self.__onSceneClosed__))
class CIP_ParenchymaSubtypeTrainingWidget(ScriptedLoadableModuleWidget):
    """Uses ScriptedLoadableModuleWidget base class, available at:
    https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
    """
    @property
    def moduleName(self):
        return "CIP_ParenchymaSubtypeTraining"

    def __init__(self, parent):
        ScriptedLoadableModuleWidget.__init__(self, parent)

        from functools import partial
        def __onNodeAddedObserver__(self, caller, eventId, callData):
            """Node added to the Slicer scene"""
            if callData.GetClassName() == 'vtkMRMLScalarVolumeNode' \
                    and slicer.util.mainWindow().moduleSelector().selectedModule == self.moduleName:
                self.__onNewVolumeLoaded__(callData)

        self.__onNodeAddedObserver__ = partial(__onNodeAddedObserver__, self)
        self.__onNodeAddedObserver__.CallDataType = vtk.VTK_OBJECT




    def setup(self):
        """This is called one time when the module GUI is initialized
        """
        ScriptedLoadableModuleWidget.setup(self)

        # Create objects that can be used anywhere in the module. Example: in most cases there should be just one
        # object of the logic class
        self.logic = CIP_ParenchymaSubtypeTrainingLogic()
        self.currentVolumeLoaded = None
        self.blockNodeEvents = False

        ##########
        # Main area
        self.mainAreaCollapsibleButton = ctk.ctkCollapsibleButton()
        self.mainAreaCollapsibleButton.text = "Main area"
        self.layout.addWidget(self.mainAreaCollapsibleButton, SlicerUtil.ALIGNMENT_VERTICAL_TOP)
        self.mainLayout = qt.QGridLayout(self.mainAreaCollapsibleButton)

        # Node selector
        volumeLabel = qt.QLabel("Active volume: ")
        volumeLabel.setStyleSheet("margin-left:5px")
        self.mainLayout.addWidget(volumeLabel, 0, 0)
        self.volumeSelector = slicer.qMRMLNodeComboBox()
        self.volumeSelector.nodeTypes = ("vtkMRMLScalarVolumeNode", "")
        self.volumeSelector.selectNodeUponCreation = True
        self.volumeSelector.autoFillBackground = True
        self.volumeSelector.addEnabled = False
        self.volumeSelector.noneEnabled = False
        self.volumeSelector.removeEnabled = False
        self.volumeSelector.showHidden = False
        self.volumeSelector.showChildNodeTypes = False
        self.volumeSelector.setMRMLScene(slicer.mrmlScene)
        self.volumeSelector.setFixedWidth(250)
        self.volumeSelector.setStyleSheet("margin: 15px 0")
        #self.volumeSelector.selectNodeUponCreation = False
        self.mainLayout.addWidget(self.volumeSelector, 0, 1, 1, 3)

        labelsStyle = "font-weight: bold; margin: 0 0 5px 5px;"
        # Types Radio Buttons
        typesLabel = qt.QLabel("Select the type")
        typesLabel.setStyleSheet(labelsStyle)
        typesLabel.setFixedHeight(15)
        self.mainLayout.addWidget(typesLabel, 1, 0)
        self.typesFrame = qt.QFrame()
        self.typesLayout = qt.QVBoxLayout(self.typesFrame)
        self.mainLayout.addWidget(self.typesFrame, 2, 0, SlicerUtil.ALIGNMENT_VERTICAL_TOP)
        self.typesRadioButtonGroup = qt.QButtonGroup()
        for key in self.logic.params.mainTypes.iterkeys():
            rbitem = qt.QRadioButton(self.logic.params.getMainTypeLabel(key))
            self.typesRadioButtonGroup.addButton(rbitem, key)
            self.typesLayout.addWidget(rbitem)
        self.typesRadioButtonGroup.buttons()[0].setChecked(True)

        # Subtypes Radio buttons
        subtypesLabel = qt.QLabel("Select the subtype")
        subtypesLabel.setStyleSheet(labelsStyle)
        subtypesLabel.setFixedHeight(15)
        self.mainLayout.addWidget(subtypesLabel, 1, 1)
        self.subtypesRadioButtonGroup = qt.QButtonGroup()
        self.subtypesFrame = qt.QFrame()
        self.subtypesFrame.setMinimumHeight(275)
        self.subtypesLayout = qt.QVBoxLayout(self.subtypesFrame)
        self.subtypesLayout.setAlignment(SlicerUtil.ALIGNMENT_VERTICAL_TOP)
        self.subtypesLayout.setStretch(0, 0)
        self.mainLayout.addWidget(self.subtypesFrame, 2, 1, SlicerUtil.ALIGNMENT_VERTICAL_TOP)     # Put the frame in the top
        # The content will be loaded dynamically every time the main type is modified

        # Artifact radio buttons
        self.artifactsLabel = qt.QLabel("Artifact")
        self.artifactsLabel.setStyleSheet(labelsStyle)
        self.artifactsLabel.setFixedHeight(15)
        self.mainLayout.addWidget(self.artifactsLabel, 1, 2)
        #self.mainLayout.addWidget(qt.QLabel("Select the artifact"), 1, 1)
        self.artifactsRadioButtonGroup = qt.QButtonGroup()
        self.artifactsFrame = qt.QFrame()
        self.artifactsLayout = qt.QVBoxLayout(self.artifactsFrame)
        self.mainLayout.addWidget(self.artifactsFrame, 2, 2, SlicerUtil.ALIGNMENT_VERTICAL_TOP)
        self.artifactsRadioButtonGroup = qt.QButtonGroup()
        for artifactId in self.logic.params.artifacts.iterkeys():
            rbitem = qt.QRadioButton(self.logic.params.getArtifactLabel(artifactId))
            self.artifactsRadioButtonGroup.addButton(rbitem, artifactId)
            self.artifactsLayout.addWidget(rbitem)
        self.artifactsRadioButtonGroup.buttons()[0].setChecked(True)

        # Load caselist button
        self.loadButton = ctk.ctkPushButton()
        self.loadButton.text = "Load fiducials file"
        self.loadButton.setIcon(qt.QIcon("{0}/open_file.png".format(SlicerUtil.CIP_ICON_DIR)))
        self.loadButton.setIconSize(qt.QSize(20, 20))
        self.loadButton.setFixedWidth(135)
        self.mainLayout.addWidget(self.loadButton, 3, 0)

        # Remove fiducial button
        self.removeLastFiducialButton = ctk.ctkPushButton()
        self.removeLastFiducialButton.text = "Remove last fiducial"
        self.removeLastFiducialButton.toolTip = "Remove the last fiducial added"
        self.removeLastFiducialButton.setIcon(qt.QIcon("{0}/delete.png".format(SlicerUtil.CIP_ICON_DIR)))
        self.removeLastFiducialButton.setIconSize(qt.QSize(20, 20))
        self.removeLastFiducialButton.setFixedWidth(200)
        self.mainLayout.addWidget(self.removeLastFiducialButton, 3, 1)

        # Save results button
        self.saveResultsButton = ctk.ctkPushButton()
        self.saveResultsButton.setText("Save markups")
        self.saveResultsButton.toolTip = "Save the markups in the specified directory"
        self.saveResultsButton.setIcon(qt.QIcon("{0}/Save.png".format(SlicerUtil.CIP_ICON_DIR)))
        self.saveResultsButton.setIconSize(qt.QSize(20,20))
        self.mainLayout.addWidget(self.saveResultsButton, 4, 0)

        # Save results directory button
        defaultPath = os.path.join(SlicerUtil.getSettingsDataFolder(self.moduleName), "Results")     # Assign a default path for the results
        path = SlicerUtil.settingGetOrSetDefault(self.moduleName, "SaveResultsDirectory", defaultPath)
        self.saveResultsDirectoryButton = ctk.ctkDirectoryButton()
        self.saveResultsDirectoryButton.directory = path
        self.saveResultsDirectoryButton.setMaximumWidth(375)
        self.mainLayout.addWidget(self.saveResultsDirectoryButton, 4, 1, 1, 2)

        #####
        # Case navigator
        if SlicerUtil.isSlicerACILLoaded():
            caseNavigatorAreaCollapsibleButton = ctk.ctkCollapsibleButton()
            caseNavigatorAreaCollapsibleButton.text = "Case navigator"
            self.layout.addWidget(caseNavigatorAreaCollapsibleButton, 0x0020)
            # caseNavigatorLayout = qt.QVBoxLayout(caseNavigatorAreaCollapsibleButton)

            # Add a case list navigator
            from ACIL.ui import CaseNavigatorWidget
            self.caseNavigatorWidget = CaseNavigatorWidget(self.moduleName, caseNavigatorAreaCollapsibleButton)
            self.caseNavigatorWidget.setup()
            # Listen for the event of loading volume
            #self.caseNavigatorWidget.addObservable(self.caseNavigatorWidget.EVENT_VOLUME_LOADED, self.onNewVolumeLoaded)

        self.layout.addStretch()

        self.updateState()

        # Connections
        self.typesRadioButtonGroup.connect("buttonClicked (QAbstractButton*)", self.__onTypesRadioButtonClicked__)
        self.subtypesRadioButtonGroup.connect("buttonClicked (QAbstractButton*)", self.__onSubtypesRadioButtonClicked__)
        self.artifactsRadioButtonGroup.connect("buttonClicked (QAbstractButton*)", self.__onSubtypesRadioButtonClicked__)

        self.volumeSelector.connect('currentNodeChanged(vtkMRMLNode*)', self.__onCurrentNodeChanged__)
        self.loadButton.connect('clicked()', self.openFiducialsFile)
        self.removeLastFiducialButton.connect('clicked()', self.__onRemoveLastFiducialButtonClicked__)
        # self.saveResultsOpenDirectoryDialogButton.connect('clicked()', self.onOpenDirectoryDialogButtonClicked)
        self.saveResultsDirectoryButton.connect("directoryChanged (str)", self.__onSaveResultsDirectoryChanged__)
        self.saveResultsButton.connect('clicked()', self.__onSaveResultsButtonClicked__)

        self.observers = []
        self.observers.append(slicer.mrmlScene.AddObserver(slicer.vtkMRMLScene.NodeAddedEvent, self.__onNodeAddedObserver__))
        self.observers.append(slicer.mrmlScene.AddObserver(slicer.vtkMRMLScene.EndCloseEvent, self.__onSceneClosed__))


    def updateState(self):
        """ Refresh the markups state, activate the right fiducials list node (depending on the
        current selected type) and creates it when necessary
        :return:
        """
        # Load the subtypes for this type
        subtypesDict = self.logic.getSubtypes(self.typesRadioButtonGroup.checkedId())
        # Remove all the existing buttons
        for b in self.subtypesRadioButtonGroup.buttons():
            b.hide()
            b.delete()
        # Add all the subtypes with the full description
        for subtype in subtypesDict.iterkeys():
            rbitem = qt.QRadioButton(self.logic.getSubtypeFullDescription(subtype))
            self.subtypesRadioButtonGroup.addButton(rbitem, subtype)
            self.subtypesLayout.addWidget(rbitem, SlicerUtil.ALIGNMENT_VERTICAL_TOP)
        # Check first element by default
        self.subtypesRadioButtonGroup.buttons()[0].setChecked(True)

        # Set the correct state for fiducials
        if self.currentVolumeLoaded is not None:
            self.logic.setActiveFiducialsListNode(self.currentVolumeLoaded,
                self.typesRadioButtonGroup.checkedId(), self.subtypesRadioButtonGroup.checkedId(), self.artifactsRadioButtonGroup.checkedId())

    def saveResultsCurrentNode(self):
        d = self.saveResultsDirectoryButton.directory
        if not os.path.isdir(d):
            # Ask the user if he wants to create the folder
            if qt.QMessageBox.question(slicer.util.mainWindow(), "Create directory?",
                "The directory '{0}' does not exist. Do you want to create it?".format(d),
                                       qt.QMessageBox.Yes|qt.QMessageBox.No) == qt.QMessageBox.Yes:
                try:
                    os.makedirs(d)
                    # Make sure that everybody has write permissions (sometimes there are problems because of umask)
                    os.chmod(d, 0777)
                    self.logic.saveCurrentFiducials(d)
                    qt.QMessageBox.information(slicer.util.mainWindow(), 'Results saved',
                        "The results have been saved succesfully")
                except:
                     qt.QMessageBox.warning(slicer.util.mainWindow(), 'Directory incorrect',
                        'The folder "{0}" could not be created. Please select a valid directory'.format(d))
        else:
            self.logic.saveCurrentFiducials(d)
            qt.QMessageBox.information(slicer.util.mainWindow(), 'Results saved',
                "The results have been saved succesfully")

    def openFiducialsFile(self):
        volumeNode = self.volumeSelector.currentNode()
        if volumeNode is None:
            qt.QMessageBox.warning(slicer.util.mainWindow(), 'Select a volume',
                                       'Please load a volume first')
            return

        f = qt.QFileDialog.getOpenFileName()
        if f:
            self.logic.loadFiducials(volumeNode, f)
            self.saveResultsDirectoryButton.directory = os.path.dirname(f)
            qt.QMessageBox.information(slicer.util.mainWindow(), "File loaded", "File loaded successfully")

    def enter(self):
        """This is invoked every time that we select this module as the active module in Slicer (not only the first time)"""
        self.blockNodeEvents = False
        # if len(self.observers) == 0:
        #     self.observers.append(slicer.mrmlScene.AddObserver(slicer.vtkMRMLScene.NodeAddedEvent, self.__onNodeAddedObserver__))
        #     self.observers.append(slicer.mrmlScene.AddObserver(slicer.vtkMRMLScene.EndCloseEvent, self.__onSceneClosed__))
        SlicerUtil.setFiducialsCursorMode(True, True)

        if self.volumeSelector.currentNodeId != "":
            SlicerUtil.setActiveVolumeId(self.volumeSelector.currentNodeId)
            self.currentVolumeLoaded = slicer.util.getNode(self.volumeSelector.currentNodeId)
            self.updateState()

    def exit(self):
        """This is invoked every time that we switch to another module (not only when Slicer is closed)."""
        try:
            self.blockNodeEvents = True
            SlicerUtil.setFiducialsCursorMode(False)
            # for observer in self.observers:
            #     slicer.mrmlScene.RemoveObserver(observer)
            #     self.observers.remove(observer)
        except:
            pass

    def cleanup(self):
        """This is invoked as a destructor of the GUI when the module is no longer going to be used"""
        try:
            for observer in self.observers:
                slicer.mrmlScene.RemoveObserver(observer)
                self.observers.remove(observer)
        except:
            pass

    def __onNewVolumeLoaded__(self, newVolumeNode):
        """ Added a new node in the scene
        :param newVolumeNode:
        :return:
        """
        self.__checkNewVolume__(newVolumeNode)
        self.blockNodeEvents = True
        self.volumeSelector.setCurrentNode(newVolumeNode)
        self.blockNodeEvents = False

    def __onCurrentNodeChanged__(self, volumeNode):
        self.__checkNewVolume__(volumeNode)
        # if volumeNode:
        #     #self.logic.reset(volumeNode)
        #     SlicerUtil.setActiveVolumeId(volumeNode.GetID())
        #     self.updateState()

    def __checkNewVolume__(self, newVolumeNode):
        if self.blockNodeEvents:
            return
        self.blockNodeEvents = True
        volume = self.currentVolumeLoaded
        if volume is not None and newVolumeNode is not None \
                and newVolumeNode.GetID() != volume.GetID()  \
                and not self.logic.isVolumeSaved(volume.GetID()):
            # Ask the user if he wants to save the previously loaded volume
            if qt.QMessageBox.question(slicer.util.mainWindow(), "Save results?",
                    "The fiducials for the volume '{0}' have not been saved. Do you want to save them?"
                    .format(volume.GetName()),
                    qt.QMessageBox.Yes|qt.QMessageBox.No) == qt.QMessageBox.Yes:
                self.saveResultsCurrentNode()
        # Remove all the previously existing nodes
        if self.currentVolumeLoaded is not None and newVolumeNode != self.currentVolumeLoaded:
            # Remove previously existing node
            self.logic.removeMarkupsAndNode(self.currentVolumeLoaded)
        if newVolumeNode is not None:
            SlicerUtil.setActiveVolumeId(newVolumeNode.GetID())
            SlicerUtil.setFiducialsCursorMode(True, True)

        self.currentVolumeLoaded = newVolumeNode
        self.updateState()
        self.blockNodeEvents = False

    def __onTypesRadioButtonClicked__(self, button):
        """ One of the radio buttons has been pressed
        :param button:
        :return:
        """
        self.updateState()

    def __onSubtypesRadioButtonClicked__(self, button):
        """ One of the subtype radio buttons has been pressed
        :param button:
        :return:
        """
        selectedVolume = self.volumeSelector.currentNode()
        if selectedVolume is not None:
            self.logic.setActiveFiducialsListNode(selectedVolume,
                    self.typesRadioButtonGroup.checkedId(), self.subtypesRadioButtonGroup.checkedId(), self.artifactsRadioButtonGroup.checkedId())

    def __onRemoveLastFiducialButtonClicked__(self):
       self.logic.removeLastMarkup()

    def __onSaveResultsDirectoryChanged__(self, directory):
        # f = qt.QFileDialog.getExistingDirectory()
        # if f:
        #     self.saveResultsDirectoryText.setText(f)
        SlicerUtil.setSetting(self.moduleName, "SaveResultsDirectory", directory)

    def __onSaveResultsButtonClicked__(self):
        self.saveResultsCurrentNode()

    def __onSceneClosed__(self, arg1, arg2):
        self.currentVolumeLoaded = None