Пример #1
0
class qSlicerReportingModuleWidget:
    def __init__(self, parent=None):

        if not parent:
            self.parent = slicer.qMRMLWidget()
            self.parent.setLayout(qt.QVBoxLayout())
            self.parent.setMRMLScene(slicer.mrmlScene)
            self.layout = self.parent.layout()
            self.setup()
            self.parent.show()
        else:
            self.parent = parent
            self.layout = parent.layout()

        # Reference to the logic that Slicer instantiated
        self.__logic = slicer.modules.reporting.logic()
        if not self.__logic:
            # create a new instance
            self.__logic = slicer.modulelogic.vtkSlicerReportingModuleLogic()

        # Get the location and initialize the DICOM DB
        settings = qt.QSettings()
        self.__dbFileName = settings.value("DatabaseDirectory", "")
        if self.__dbFileName == "":
            Helper.Warning("DICOM Database is not accessible.")
        else:
            self.__dbFileName = self.__dbFileName + "/ctkDICOM.sql"

            if self.__logic.InitializeDICOMDatabase(self.__dbFileName):
                Helper.Info('DICOM database initialized correctly!')
            else:
                Helper.Error('Failed to initialize DICOM database at ' +
                             self.__dbFileName)

        if not self.__logic.GetMRMLScene():
            # set the logic's mrml scene
            self.__logic.SetMRMLScene(slicer.mrmlScene)

        # for export
        self.exportFileName = None
        self.exportFileDialog = None

        # initialize parameter node
        self.__parameterNode = None
        nNodes = slicer.mrmlScene.GetNumberOfNodesByClass(
            'vtkMRMLScriptedModuleNode')
        for n in xrange(nNodes):
            compNode = slicer.mrmlScene.GetNthNodeByClass(
                n, 'vtkMRMLScriptedModuleNode')
            compNode.SetReferenceCount(compNode.GetReferenceCount() - 1)
            nodeid = None
            if compNode.GetModuleName() == 'Reporting':
                self.__parameterNode = compNode
                'Found existing Reporting parameter node'
                break
        if self.__parameterNode == None:
            self.__parameterNode = slicer.vtkMRMLScriptedModuleNode()
            self.__parameterNode.SetModuleName('Reporting')
            self.__parameterNode.SetSingletonTag('Reporting')
            slicer.mrmlScene.AddNode(self.__parameterNode)

            # keep active report and volume
            self.__rNode = None
            self.__vNode = None

        if self.__parameterNode != None:
            paramID = self.__parameterNode.GetID()
            self.__logic.SetActiveParameterNodeID(paramID)
        else:
            Helper.Error('Unable to set logic active parameter node')

        # TODO: figure out why module/class hierarchy is different
        # between developer builds ans packages
        try:
            # for developer build...
            self.editUtil = EditorLib.EditUtil.EditUtil()
        except AttributeError:
            # for release package...
            self.editUtil = EditorLib.EditUtil()

    def setup(self):
        #
        # Input frame
        #
        self.__inputFrame = ctk.ctkCollapsibleButton()
        self.__inputFrame.text = "Input"
        self.__inputFrame.collapsed = 0
        inputFrameLayout = qt.QFormLayout(self.__inputFrame)

        self.layout.addWidget(self.__inputFrame)

        # Active report node
        label = qt.QLabel('Report: ')
        self.__reportSelector = slicer.qMRMLNodeComboBox()
        self.__reportSelector.nodeTypes = ['vtkMRMLReportingReportNode']
        self.__reportSelector.setMRMLScene(slicer.mrmlScene)
        self.__reportSelector.addEnabled = 1
        self.__reportSelector.removeEnabled = 0

        inputFrameLayout.addRow(label, self.__reportSelector)

        self.__reportSelector.connect('mrmlSceneChanged(vtkMRMLScene*)',
                                      self.onMRMLSceneChanged)
        self.__reportSelector.connect('currentNodeChanged(vtkMRMLNode*)',
                                      self.onReportNodeChanged)

        # Volume being annotated (only one is allowed for the report)
        label = qt.QLabel(
            'NOTE: Only volumes loaded from DICOM can be annotated!')
        inputFrameLayout.addRow(label)
        label = qt.QLabel('Annotated volume: ')
        self.__volumeSelector = slicer.qMRMLNodeComboBox()
        self.__volumeSelector.nodeTypes = ['vtkMRMLScalarVolumeNode']
        # only allow volumes with the attribute DICOM.instanceUIDs
        self.__volumeSelector.addAttribute('vtkMRMLScalarVolumeNode',
                                           'DICOM.instanceUIDs')
        self.__volumeSelector.setMRMLScene(slicer.mrmlScene)
        self.__volumeSelector.addEnabled = False

        inputFrameLayout.addRow(label, self.__volumeSelector)

        self.__volumeSelector.connect('currentNodeChanged(vtkMRMLNode*)',
                                      self.onAnnotatedVolumeNodeChanged)
        self.__volumeSelector.connect('mrmlSceneChanged(vtkMRMLScene*)',
                                      self.onMRMLSceneChanged)

        #
        # Annotation frame -- vocabulary-based description of what is
        # being annotated/marked up in this report
        #
        self.__annotationsFrame = ctk.ctkCollapsibleButton()
        self.__annotationsFrame.text = "Annotation"
        self.__annotationsFrame.collapsed = 0
        annotationsFrameLayout = qt.QFormLayout(self.__annotationsFrame)

        self.layout.addWidget(self.__annotationsFrame)

        self.__defaultColorNode = self.__logic.GetDefaultColorNode()

        self.__toolsColor = EditColor(self.__annotationsFrame,
                                      colorNode=self.__defaultColorNode)

        #
        # Markup frame -- summary of all the markup elements contained in the
        # report
        #
        self.__markupFrame = ctk.ctkCollapsibleButton()
        self.__markupFrame.text = "Markup"
        self.__markupFrame.collapsed = 0
        markupFrameLayout = qt.QFormLayout(self.__markupFrame)

        self.layout.addWidget(self.__markupFrame)

        # Add a flag to switch between different tree view models
        self.__useNewTreeView = 1

        # Add the tree widget
        if self.__useNewTreeView == 1:
            self.__markupTreeView = slicer.modulewidget.qMRMLReportingTreeView(
            )
            self.__markupTreeView.sceneModelType = "DisplayableHierarchy"
        else:
            self.__markupTreeView = slicer.qMRMLTreeView()
            self.__markupTreeView.sceneModelType = "Displayable"
        self.__markupTreeView.setMRMLScene(self.__logic.GetMRMLScene())

        self.__markupSliceText = qt.QLabel()
        markupFrameLayout.addRow(self.__markupSliceText)
        markupFrameLayout.addRow(self.__markupTreeView)

        # Editor frame
        self.__editorFrame = ctk.ctkCollapsibleButton()
        self.__editorFrame.text = 'Segmentation'
        self.__editorFrame.collapsed = 0
        editorFrameLayout = qt.QFormLayout(self.__editorFrame)

        label = qt.QLabel('Segmentation volume: ')
        self.__segmentationSelector = slicer.qMRMLNodeComboBox()
        self.__segmentationSelector.nodeTypes = ['vtkMRMLScalarVolumeNode']
        self.__segmentationSelector.setMRMLScene(slicer.mrmlScene)
        self.__segmentationSelector.addEnabled = 1
        self.__segmentationSelector.noneEnabled = 1
        self.__segmentationSelector.removeEnabled = 0
        self.__segmentationSelector.showHidden = 0
        self.__segmentationSelector.showChildNodeTypes = 0
        self.__segmentationSelector.selectNodeUponCreation = 1
        self.__segmentationSelector.addAttribute('vtkMRMLScalarVolumeNode',
                                                 'LabelMap', 1)

        editorFrameLayout.addRow(label, self.__segmentationSelector)

        editorWidgetParent = slicer.qMRMLWidget()
        editorWidgetParent.setLayout(qt.QVBoxLayout())
        editorWidgetParent.setMRMLScene(slicer.mrmlScene)
        self.__editorWidget = EditorWidget(parent=editorWidgetParent,
                                           showVolumesFrame=False)
        self.__editorWidget.setup()
        self.__editorWidget.toolsColor.frame.setVisible(False)
        editorFrameLayout.addRow(editorWidgetParent)

        markupFrameLayout.addRow(self.__editorFrame)

        self.__segmentationSelector.connect('currentNodeChanged(vtkMRMLNode*)',
                                            self.onSegmentationNodeChanged)
        self.__segmentationSelector.connect('mrmlSceneChanged(vtkMRMLScene*)',
                                            self.onMRMLSceneChanged)

        # IO frame
        self.__ioFrame = ctk.ctkCollapsibleButton()
        self.__ioFrame.text = 'Import/Export'
        self.__ioFrame.collapsed = 1
        ioFrameLayout = qt.QGridLayout(self.__ioFrame)

        self.layout.addWidget(self.__ioFrame)

        # Buttons to save/load report using AIM XML serialization
        label = qt.QLabel('Export folder')
        self.__exportFolderPicker = ctk.ctkDirectoryButton()
        exportButton = qt.QPushButton('Export')
        exportButton.connect('clicked()', self.onReportExport)
        ioFrameLayout.addWidget(label, 0, 0)
        ioFrameLayout.addWidget(self.__exportFolderPicker, 0, 1)
        ioFrameLayout.addWidget(exportButton, 0, 2)

        label = qt.QLabel('AIM file to import')
        self.__aimFilePicker = qt.QPushButton('N/A')
        self.__aimFilePicker.connect('clicked()', self.onSelectAIMFile)
        button = qt.QPushButton('Import')
        button.connect('clicked()', self.onReportImport)
        ioFrameLayout.addWidget(label, 1, 0)
        ioFrameLayout.addWidget(self.__aimFilePicker, 1, 1)
        ioFrameLayout.addWidget(button, 1, 2)
        self.__importAIMFile = None

        self.__reportSelector.connect('currentNodeChanged(vtkMRMLNode*)',
                                      self.updateWidgets)
        self.__volumeSelector.connect('currentNodeChanged(vtkMRMLNode*)',
                                      self.updateWidgets)
        self.updateWidgets()

        self.layout.addStretch(1)

        self.__editorParameterNode = self.editUtil.getParameterNode()

        self.__editorParameterNode.AddObserver(
            vtk.vtkCommand.ModifiedEvent, self.onEditorParameterNodeChanged)

    def onEditorParameterNodeChanged(self, caller, event):
        print('Editor parameter node changed')
        label = self.__editorParameterNode.GetParameter('label')
        if self.__rNode:
            print("Updating report label")
            self.__rNode.SetFindingLabel(int(label))

    def enter(self):
        # switch to Two-over-Two layout
        lm = slicer.app.layoutManager()
        lm.setLayout(26)  # two over two

        # update the logic to know that the module has been entered
        self.__logic.GUIHiddenOff()
        self.updateWidgetFromParameters()

        # respond to error events
        Helper.Info(
            'Enter: Setting up connection to respond to logic error events')
        hasObserver = self.__logic.HasObserver(vtk.vtkCommand.ErrorEvent)
        if hasObserver == 0:
            tag = self.__logic.AddObserver(vtk.vtkCommand.ErrorEvent,
                                           self.respondToErrorMessage)
            # print '\tobserver tag = ',tag
        else:
            Helper.Debug('Logic already has an observer on the ErrorEvent')

        vnode = self.__volumeSelector.currentNode()
        if vnode != None:
            # print "Enter: setting active hierarchy from node ",vnode.GetID()
            # update the logic active markup
            self.__logic.SetActiveMarkupHierarchyIDFromNode(vnode)
            self.updateTreeView()

        self.__editorWidget.enter()

    def exit(self):
        self.updateParametersFromWidget()

        Helper.Debug(
            "Reporting Exit. Letting logic know that module has been exited")
        # let the module logic know that the GUI is hidden, so that fiducials can go elsewehre
        self.__logic.GUIHiddenOn()
        # disconnect observation
        self.__logic.RemoveObservers(vtk.vtkCommand.ErrorEvent)

        self.__editorWidget.exit()

    # respond to error events from the logic
    def respondToErrorMessage(self, caller, event):
        errorMessage = self.__logic.GetErrorMessage()
        # inform user only if the message is not empty since vtkErrorMacro invokes this event as well
        if errorMessage != None:
            Helper.Debug('respondToErrorMessage, event = ' + str(event) +
                         ', message =\n\t' + str(errorMessage))
            errorDialog = qt.QErrorMessage(self.parent)
            errorDialog.showMessage(errorMessage)

    def onMRMLSceneChanged(self, mrmlScene):
        if mrmlScene != self.__logic.GetMRMLScene():
            self.__logic.SetMRMLScene(mrmlScene)
            self.__logic.RegisterNodes()
        self.__reportSelector.setMRMLScene(slicer.mrmlScene)

    def updateTreeView(self):
        # make the tree view update
        if self.__useNewTreeView == 1:
            self.__markupTreeView.updateTreeView()
        else:
            self.__markupTreeView.sceneModelType = "Displayable"
            nodeTypes = [
                'vtkMRMLDisplayableHierarchyNode',
                'vtkMRMLAnnotationHierarchyNode', 'vtkMRMLAnnotationNode',
                'vtkMRMLVolumeNode', 'vtkMRMLReportingReportNode'
            ]
            self.__markupTreeView.nodeTypes = nodeTypes
            self.__markupTreeView.listenNodeModifiedEvent = 1
            self.__markupTreeView.sceneModelType = "Displayable"
            # show these nodes even if they're hidden by being children of hidden hierarchy nodes
            showHiddenNodeTypes = [
                'vtkMRMLAnnotationNode', 'vtkMRMLVolumeNode',
                'vtkMRMLDisplayableHierarchyNode'
            ]
            self.__markupTreeView.model(
            ).showHiddenForTypes = showHiddenNodeTypes
        # set the root to be the current report hierarchy root
        if self.__rNode == None:
            Helper.Error("updateTreeView: report node is not initialized!")
            self.__markupTreeView.setRootNode(None)
            return
        else:
            # the tree root node has to be a hierarchy node, so get the associated hierarchy node for the active report node
            rootNode = slicer.vtkMRMLHierarchyNode(
            ).GetAssociatedHierarchyNode(self.__rNode.GetScene(),
                                         self.__rNode.GetID())
            if rootNode:
                self.__markupTreeView.setRootNode(rootNode)
                Helper.Debug("Setting tree view root to be " +
                             rootNode.GetID())
                self.__markupTreeView.expandAll()
            else:
                Helper.Debug("Setting tree view root to be None")
                self.__markupTreeView.setRootNode(None)

    def onAnnotatedVolumeNodeChanged(self):
        Helper.Debug("onAnnotatedVolumeNodeChanged()")

        # get the current volume node
        selectedVolume = self.__volumeSelector.currentNode()

        # do the error checks
        if selectedVolume == None or self.__rNode == None:
            self.__volumeSelector.setCurrentNode(None)
            return

        uids = selectedVolume.GetAttribute('DICOM.instanceUIDs')
        if uids == None:
            Helper.ErrorPopup(
                "Volume \"" + selectedVolume.GetName() +
                "\" was not loaded from DICOM. Only volumes loaded from DICOM data can be annotated in the Reporting module."
            )
            self.__volumeSelector.setCurrentNode(None)
            return

        nSlices = selectedVolume.GetImageData().GetExtent()[-1] + 1
        if nSlices != len(string.split(uids)):
            Helper.ErrorPopup(
                "Volume \"" + selectedVolume.GetName() +
                "\" was loaded from multi-frame DICOM. Multi-frame DICOM is currently not supported by the Reporting module"
            )
            self.__volumeSelector.setCurrentNode(None)
            return

        # volume node is valid!
        self.__vNode = selectedVolume

        # update the report node
        if self.__rNode != None:
            self.__rNode.SetVolumeNodeID(self.__vNode.GetID())
            self.__rNode.SetName('Report for Volume ' + self.__vNode.GetName())

        Helper.SetBgFgVolumes(self.__vNode.GetID(), '')
        Helper.RotateToVolumePlanes()

        # go over all label nodes in the scene
        # if there is a label that has the selected volume as associated node,
        #   initialize label selector to show that label
        volumeNodes = slicer.mrmlScene.GetNodesByClass(
            'vtkMRMLScalarVolumeNode')
        volumeNodes.SetReferenceCount(volumeNodes.GetReferenceCount() - 1)
        associatedLabelFound = False
        for i in range(volumeNodes.GetNumberOfItems()):
            vol = volumeNodes.GetItemAsObject(i)
            associatedNodeID = vol.GetAttribute('AssociatedNodeID')
            label = vol.GetAttribute('LabelMap')
            if associatedNodeID == self.__vNode.GetID() and label == '1':
                Helper.SetLabelVolume(vol.GetID())
                associatedLabelFound = True

        # if there is no associated label node, set the selector to none
        if associatedLabelFound == False:
            Helper.SetLabelVolume("")

        orientation = Helper.GetScanOrderSliceName(self.__vNode)
        message = "Slice viewers to be used for markup: "
        for sliceViewer in orientation:
            message = message + sliceViewer
            if orientation.index(sliceViewer) < (len(orientation) - 1):
                message = message + ", "
        Helper.Debug(message)
        self.__markupSliceText.text = message

        # take the first one
        self.__parameterNode.SetParameter('acquisitionSliceViewer',
                                          orientation[0])

        # print "Calling logic to set up hierarchy"
        self.__logic.InitializeHierarchyForVolume(self.__vNode)
        self.updateTreeView()

    def onSegmentationNodeChanged(self):
        Helper.Debug('onSegmentationNodeChanged()')

        if self.__vNode == None:
            Helper.Error(
                'Should not be possible to select segmentation unless annotated volume is initialized!'
            )
            return

        # get the current segmentation (label) node
        sNode = self.__segmentationSelector.currentNode()
        if sNode == None:
            self.updateWidgets()
            return

        # if it's a new label, it should have/will be added to the report
        # automatically
        image = sNode.GetImageData()
        if image == None:
            Helper.initializeNewLabel(sNode, self.__vNode)
        else:
            # if it's an existing label, we need to check that the geometry matches
            # the annotated label geometry, and if so, add it to the hierarchy
            if Helper.GeometriesMatch(sNode, self.__vNode) == False:
                Helper.ErrorPopup(
                    'The geometry of the segmentation label you attempted to select does not match the geometry of the volume being annotated! Please select a different label or create a new one.'
                )
                self.__segmentationSelector.setCurrentNode(None)
                self.updateWidgets()
                return

        # assign the color LUT we use
        dNode = sNode.GetDisplayNode()
        dNode.SetAndObserveColorNodeID(self.__defaultColorNode.GetID())

        sNode.SetAttribute('AssociatedNodeID', self.__vNode.GetID())
        self.__logic.AddNodeToReport(sNode)

        # assign the volume and the selected color to the editor parameter node
        Helper.SetLabelVolume(sNode.GetID())

        # TODO: disable adding new label node to the hierarchy if it was added
        # outside the reporting module

        self.__segmentationSelector.setCurrentNode(sNode)

        self.__editorWidget.setMasterNode(self.__vNode)
        self.__editorWidget.setMergeNode(sNode)

        self.__editorParameterNode.Modified()

        self.updateWidgets()

    def onReportNodeChanged(self):
        Helper.Debug("onReportNodeChanged()")

        # cancel the active effect in Editor
        if self.__editorWidget:
            self.__editorWidget.toolsBox.defaultEffect()
        # TODO
        #  -- initialize annotations and markup frames based on the report node
        #  content
        self.__rNode = self.__reportSelector.currentNode()

        self.__segmentationSelector.setCurrentNode(None)
        # disable editor frame
        self.onSegmentationNodeChanged()

        if self.__rNode != None:

            Helper.Debug("Selected report has changed to " +
                         self.__rNode.GetID())

            if self.__rNode.GetDICOMDatabaseFileName() == "":
                self.__rNode.SetDICOMDatabaseFileName(self.__dbFileName)

            self.__parameterNode.SetParameter('reportID', self.__rNode.GetID())

            # setup the default color node, if not initialized
            if self.__rNode.GetColorNodeID() == "":
                self.__rNode.SetColorNodeID(self.__defaultColorNode.GetID())
                Helper.Debug('Set color node id to ' +
                             self.__defaultColorNode.GetID())
            else:
                Helper.Debug('Color node has already been set to ' +
                             self.__rNode.GetColorNodeID())

            self.__logic.InitializeHierarchyForReport(self.__rNode)
            self.updateTreeView()
            vID = self.__rNode.GetVolumeNodeID()
            if vID:
                Helper.Debug('Have a volume node id in the report ' + vID +
                             ', setting current volume node selector')
                self.__vNode = slicer.mrmlScene.GetNodeByID(vID)
                self.__volumeSelector.setCurrentNode(self.__vNode)
            else:
                Helper.Debug(
                    'Do not have a volume id in the report, setting current volume node selector to none'
                )
                # set the volume to be none
                self.__vNode = None
                self.__volumeSelector.setCurrentNode(None)

            # hide the markups that go with other report nodes
            self.__logic.HideAnnotationsForOtherReports(self.__rNode)

            # update the GUI annotation name/label/color
            self.updateWidgets()

            # initialize the label used by the EditorWidget
            if self.__editorParameterNode:
                self.__editorParameterNode.SetParameter(
                    'label', str(self.__rNode.GetFindingLabel()))

    '''
  Load report and initialize GUI based on .xml report file content
  '''

    def onReportImport(self):

        print('onReportImport here!!!')
        # TODO
        #  -- popup file open dialog to choose the .xml AIM file
        #  -- warn the user if the selected report node is not empty
        #  -- populate the selected report node, initializing annotation template,
        #  content, markup hierarchy and content
        if not self.__importAIMFile:
            Helper.Debug('onReportImport: import file name not specified')
            return

        Helper.Debug('onReportImport')

        # For now, always create a new report node
        newReport = slicer.modulemrml.vtkMRMLReportingReportNode()

        # always use the default color map
        newReport.SetColorNodeID(self.__defaultColorNode.GetID())

        slicer.mrmlScene.AddNode(newReport)
        self.__reportSelector.setCurrentNode(newReport)
        self.onReportNodeChanged()

        # initialize the report hierarchy
        #  -- assume that report node has been created and is in the selector

        Helper.LoadAIMFile(newReport.GetID(), self.__importAIMFile)

        # update the GUI
        Helper.Debug('onReportImport --> calling onReportNodeChanged()')
        self.onReportNodeChanged()

    def onSelectAIMFile(self):
        #  -- popup file dialog prompting output file
        if not self.__importAIMFile:
            fileName = qt.QFileDialog.getOpenFileName(self.parent,
                                                      "Choose AIM report", "/",
                                                      "XML Files (*.xml)")
        else:
            lastDir = self.__importAIMFile[0:string.
                                           rfind(self.__importAIMFile, '/')]
            fileName = qt.QFileDialog.getOpenFileName(self.parent,
                                                      "Choose AIM report",
                                                      lastDir,
                                                      "XML Files (*.xml)")

        if fileName == '':
            return

        self.__importAIMFile = fileName
        try:
            label = string.split(fileName, '/')[-1]
        except:
            label = fileName
        self.__aimFilePicker.text = label

    '''
  Save report to an xml file
  '''

    def onReportExport(self):
        if self.__rNode == None:
            return

        Helper.Debug('onReportExport')

        exportDirectory = self.__exportFolderPicker.directory
        self.__rNode.SetStorageDirectoryName(exportDirectory)

        Helper.Debug('Will export to ' + exportDirectory)

        # use the currently selected report
        self.__rNode = self.__reportSelector.currentNode()
        if self.__rNode == None:
            return

        #  -- traverse markup hierarchy and translate
        retval = self.__logic.SaveReportToAIM(self.__rNode)
        if retval == EXIT_FAILURE:
            Helper.Error("Failed to save report to '" + exportDirectory + "'")
        else:
            Helper.Debug("Saved report to '" + exportDirectory + "'")

        # This code is commented out because repeated insertion into the database
        # leads to database lockout (illustrated in Testing/RepeatInsertTest.py).
        # For now, the solution will be to import the created DICOM objects
        # manually.
        # attempt to insert each of the saved items into the database
        #filesToInsert = glob.glob(exportDirectory+'/*')
        #for f in filesToInsert:
        #  Helper.Debug('Inserting '+f+' into DICOM database...')
        #  slicer.dicomDatabase.insert(f)
        #  Helper.Debug('Done')

    def updateWidgetFromParameters(self):
        pn = self.__parameterNode
        if pn == None:
            return
        reportID = pn.GetParameter('reportID')

        if reportID != None:
            self.__rNode = Helper.getNodeByID(reportID)
            # AF: looks like this does not trigger event, why?
            self.__reportSelector.setCurrentNode(self.__rNode)

    def updateParametersFromWidget(self):
        pn = self.__parameterNode
        if pn == None:
            return

        report = self.__reportSelector.currentNode()

        if report != None:
            pn.SetParameter('reportID', report.GetID())

    def updateWidgets(self):

        Helper.Debug("updateWidgets()")

        report = self.__rNode
        volume = None
        label = self.__segmentationSelector.currentNode()

        if report != None:
            self.__reportSelector.setCurrentNode(self.__rNode)
            volume = slicer.mrmlScene.GetNodeByID(report.GetVolumeNodeID())
            # TODO: get the label node from volume hieararchy

            if volume != None:
                self.__volumeSelector.setCurrentNode(volume)
                self.__markupFrame.enabled = 1
                self.__annotationsFrame.enabled = 1
                self.__volumeSelector.enabled = 0
            else:
                self.__volumeSelector.enabled = 1
                self.__markupFrame.enabled = 0
                self.__annotationsFrame.enabled = 0

        else:
            self.__volumeSelector.enabled = 0
            self.__markupFrame.enabled = 0
            self.__annotationsFrame.enabled = 0

        if not label:
            self.__editorWidget.editLabelMapsFrame.collapsed = True
            self.__editorWidget.editLabelMapsFrame.setEnabled(False)
        else:
            self.__editorWidget.editLabelMapsFrame.collapsed = False
            self.__editorWidget.editLabelMapsFrame.setEnabled(True)
class qSlicerReportingModuleWidget:
  def __init__( self, parent=None ):

    if not parent:
      self.parent = slicer.qMRMLWidget()
      self.parent.setLayout( qt.QVBoxLayout())
      self.parent.setMRMLScene(slicer.mrmlScene)
      self.layout = self.parent.layout()
      self.setup()
      self.parent.show()
    else:
      self.parent = parent
      self.layout = parent.layout()

    # Reference to the logic that Slicer instantiated
    self.__logic  = slicer.modules.reporting.logic()
    if not self.__logic:
      # create a new instance
      self.__logic = slicer.modulelogic.vtkSlicerReportingModuleLogic()

    # Get the location and initialize the DICOM DB
    settings = qt.QSettings()
    self.__dbFileName = settings.value("DatabaseDirectory","")
    if self.__dbFileName == "":
      Helper.Warning("DICOM Database is not accessible.")
    else:
      self.__dbFileName = self.__dbFileName+"/ctkDICOM.sql"

      if self.__logic.InitializeDICOMDatabase(self.__dbFileName):
        Helper.Info('DICOM database initialized correctly!')
      else:
        Helper.Error('Failed to initialize DICOM database at '+self.__dbFileName)

    if not self.__logic.GetMRMLScene():
      # set the logic's mrml scene
      self.__logic.SetMRMLScene(slicer.mrmlScene)

    # for export
    self.exportFileName = None
    self.exportFileDialog = None

    # initialize parameter node
    self.__parameterNode = None
    nNodes = slicer.mrmlScene.GetNumberOfNodesByClass('vtkMRMLScriptedModuleNode')
    for n in xrange(nNodes):
      compNode = slicer.mrmlScene.GetNthNodeByClass(n, 'vtkMRMLScriptedModuleNode')
      compNode.SetReferenceCount(compNode.GetReferenceCount() - 1)
      nodeid = None
      if compNode.GetModuleName() == 'Reporting':
        self.__parameterNode = compNode
        'Found existing Reporting parameter node'
        break
    if self.__parameterNode == None:
      self.__parameterNode = slicer.vtkMRMLScriptedModuleNode()
      self.__parameterNode.SetModuleName('Reporting')
      self.__parameterNode.SetSingletonTag('Reporting')
      slicer.mrmlScene.AddNode(self.__parameterNode)

      # keep active report and volume
      self.__rNode = None
      self.__vNode = None

    if self.__parameterNode != None:
      paramID = self.__parameterNode.GetID()
      self.__logic.SetActiveParameterNodeID(paramID)
    else:
      Helper.Error('Unable to set logic active parameter node')
    
    # TODO: figure out why module/class hierarchy is different
    # between developer builds ans packages
    try:
      # for developer build...
      self.editUtil = EditorLib.EditUtil.EditUtil()
    except AttributeError:
      # for release package...
      self.editUtil = EditorLib.EditUtil()

  def setup( self ):
    #
    # Input frame
    #
    self.__inputFrame = ctk.ctkCollapsibleButton()
    self.__inputFrame.text = "Input"
    self.__inputFrame.collapsed = 0
    inputFrameLayout = qt.QFormLayout(self.__inputFrame)
    
    self.layout.addWidget(self.__inputFrame)

    # Active report node
    label = qt.QLabel('Report: ')
    self.__reportSelector = slicer.qMRMLNodeComboBox()
    self.__reportSelector.nodeTypes =  ['vtkMRMLReportingReportNode']
    self.__reportSelector.setMRMLScene(slicer.mrmlScene)
    self.__reportSelector.addEnabled = 1
    self.__reportSelector.removeEnabled = 0
    
    inputFrameLayout.addRow(label, self.__reportSelector)

    self.__reportSelector.connect('mrmlSceneChanged(vtkMRMLScene*)', self.onMRMLSceneChanged)
    self.__reportSelector.connect('currentNodeChanged(vtkMRMLNode*)', self.onReportNodeChanged)
 
    # Volume being annotated (only one is allowed for the report)
    label = qt.QLabel('NOTE: Only volumes loaded from DICOM can be annotated!')
    inputFrameLayout.addRow(label)
    label = qt.QLabel('Annotated volume: ')
    self.__volumeSelector = slicer.qMRMLNodeComboBox()
    self.__volumeSelector.nodeTypes = ['vtkMRMLScalarVolumeNode']
    # only allow volumes with the attribute DICOM.instanceUIDs
    self.__volumeSelector.addAttribute('vtkMRMLScalarVolumeNode','DICOM.instanceUIDs')
    self.__volumeSelector.setMRMLScene(slicer.mrmlScene)
    self.__volumeSelector.addEnabled = False
    
    inputFrameLayout.addRow(label, self.__volumeSelector)

    self.__volumeSelector.connect('currentNodeChanged(vtkMRMLNode*)', self.onAnnotatedVolumeNodeChanged)
    self.__volumeSelector.connect('mrmlSceneChanged(vtkMRMLScene*)', self.onMRMLSceneChanged)

    #
    # Annotation frame -- vocabulary-based description of what is
    # being annotated/marked up in this report
    #
    self.__annotationsFrame = ctk.ctkCollapsibleButton()
    self.__annotationsFrame.text = "Annotation"
    self.__annotationsFrame.collapsed = 0
    annotationsFrameLayout = qt.QFormLayout(self.__annotationsFrame)
    
    self.layout.addWidget(self.__annotationsFrame)

    self.__defaultColorNode = self.__logic.GetDefaultColorNode()

    self.__toolsColor = EditColor(self.__annotationsFrame,colorNode=self.__defaultColorNode)

    #
    # Markup frame -- summary of all the markup elements contained in the
    # report
    #
    self.__markupFrame = ctk.ctkCollapsibleButton()
    self.__markupFrame.text = "Markup"
    self.__markupFrame.collapsed = 0
    markupFrameLayout = qt.QFormLayout(self.__markupFrame)
    
    self.layout.addWidget(self.__markupFrame)

    # Add a flag to switch between different tree view models
    self.__useNewTreeView = 1

    # Add the tree widget
    if self.__useNewTreeView == 1:
      self.__markupTreeView = slicer.modulewidget.qMRMLReportingTreeView()
      self.__markupTreeView.sceneModelType = "DisplayableHierarchy"
    else:
      self.__markupTreeView = slicer.qMRMLTreeView()
      self.__markupTreeView.sceneModelType = "Displayable"
    self.__markupTreeView.setMRMLScene(self.__logic.GetMRMLScene())
        
    self.__markupSliceText = qt.QLabel()
    markupFrameLayout.addRow(self.__markupSliceText)
    markupFrameLayout.addRow(self.__markupTreeView)

    # Editor frame
    self.__editorFrame = ctk.ctkCollapsibleButton()
    self.__editorFrame.text = 'Segmentation'
    self.__editorFrame.collapsed = 0
    editorFrameLayout = qt.QFormLayout(self.__editorFrame)

    label = qt.QLabel('Segmentation volume: ')
    self.__segmentationSelector = slicer.qMRMLNodeComboBox()
    self.__segmentationSelector.nodeTypes = ['vtkMRMLScalarVolumeNode']
    self.__segmentationSelector.setMRMLScene(slicer.mrmlScene)
    self.__segmentationSelector.addEnabled = 1
    self.__segmentationSelector.noneEnabled = 1
    self.__segmentationSelector.removeEnabled = 0
    self.__segmentationSelector.showHidden = 0
    self.__segmentationSelector.showChildNodeTypes = 0
    self.__segmentationSelector.selectNodeUponCreation = 1
    self.__segmentationSelector.addAttribute('vtkMRMLScalarVolumeNode','LabelMap',1)

    editorFrameLayout.addRow(label, self.__segmentationSelector)

    editorWidgetParent = slicer.qMRMLWidget()
    editorWidgetParent.setLayout(qt.QVBoxLayout())
    editorWidgetParent.setMRMLScene(slicer.mrmlScene)
    self.__editorWidget = EditorWidget(parent=editorWidgetParent,showVolumesFrame=False)
    self.__editorWidget.setup()
    self.__editorWidget.toolsColor.frame.setVisible(False)
    editorFrameLayout.addRow(editorWidgetParent)

    markupFrameLayout.addRow(self.__editorFrame)

    self.__segmentationSelector.connect('currentNodeChanged(vtkMRMLNode*)', self.onSegmentationNodeChanged)
    self.__segmentationSelector.connect('mrmlSceneChanged(vtkMRMLScene*)', self.onMRMLSceneChanged)

    # IO frame
    self.__ioFrame = ctk.ctkCollapsibleButton()
    self.__ioFrame.text = 'Import/Export'
    self.__ioFrame.collapsed = 1
    ioFrameLayout = qt.QGridLayout(self.__ioFrame)

    self.layout.addWidget(self.__ioFrame)

    # Buttons to save/load report using AIM XML serialization
    label = qt.QLabel('Export folder')
    self.__exportFolderPicker = ctk.ctkDirectoryButton()
    exportButton = qt.QPushButton('Export')
    exportButton.connect('clicked()', self.onReportExport)
    ioFrameLayout.addWidget(label,0,0)
    ioFrameLayout.addWidget(self.__exportFolderPicker,0,1)
    ioFrameLayout.addWidget(exportButton,0,2)

    label = qt.QLabel('AIM file to import')
    self.__aimFilePicker = qt.QPushButton('N/A')
    self.__aimFilePicker.connect('clicked()',self.onSelectAIMFile)
    button = qt.QPushButton('Import')
    button.connect('clicked()', self.onReportImport)
    ioFrameLayout.addWidget(label,1,0)
    ioFrameLayout.addWidget(self.__aimFilePicker,1,1)
    ioFrameLayout.addWidget(button,1,2)
    self.__importAIMFile = None

    self.__reportSelector.connect('currentNodeChanged(vtkMRMLNode*)', self.updateWidgets)
    self.__volumeSelector.connect('currentNodeChanged(vtkMRMLNode*)', self.updateWidgets)
    self.updateWidgets()

    self.layout.addStretch(1)

    self.__editorParameterNode = self.editUtil.getParameterNode()

    self.__editorParameterNode.AddObserver(vtk.vtkCommand.ModifiedEvent, self.onEditorParameterNodeChanged)

  def onEditorParameterNodeChanged(self,caller,event):
    print('Editor parameter node changed')
    label = self.__editorParameterNode.GetParameter('label')
    if self.__rNode:
      print("Updating report label")
      self.__rNode.SetFindingLabel(int(label))

  def enter(self):
    # switch to Two-over-Two layout
    lm = slicer.app.layoutManager()
    lm.setLayout(26) # two over two

    # update the logic to know that the module has been entered
    self.__logic.GUIHiddenOff()
    self.updateWidgetFromParameters()

    # respond to error events
    Helper.Info('Enter: Setting up connection to respond to logic error events')
    hasObserver = self.__logic.HasObserver(vtk.vtkCommand.ErrorEvent)
    if hasObserver == 0:
      tag = self.__logic.AddObserver(vtk.vtkCommand.ErrorEvent, self.respondToErrorMessage)
      # print '\tobserver tag = ',tag
    else:
      Helper.Debug('Logic already has an observer on the ErrorEvent')

    vnode = self.__volumeSelector.currentNode()
    if vnode != None:
      # print "Enter: setting active hierarchy from node ",vnode.GetID()
      # update the logic active markup
      self.__logic.SetActiveMarkupHierarchyIDFromNode(vnode)
      self.updateTreeView()

    self.__editorWidget.enter()


  def exit(self):
    self.updateParametersFromWidget()

    Helper.Debug("Reporting Exit. Letting logic know that module has been exited")
    # let the module logic know that the GUI is hidden, so that fiducials can go elsewehre
    self.__logic.GUIHiddenOn()
    # disconnect observation
    self.__logic.RemoveObservers(vtk.vtkCommand.ErrorEvent)

    self.__editorWidget.exit()

  # respond to error events from the logic
  def respondToErrorMessage(self, caller, event):
    errorMessage = self.__logic.GetErrorMessage()
    # inform user only if the message is not empty since vtkErrorMacro invokes this event as well
    if errorMessage != None:
      Helper.Debug('respondToErrorMessage, event = '+str(event)+', message =\n\t'+str(errorMessage))
      errorDialog = qt.QErrorMessage(self.parent)
      errorDialog.showMessage(errorMessage)

  def onMRMLSceneChanged(self, mrmlScene):
    if mrmlScene != self.__logic.GetMRMLScene():
      self.__logic.SetMRMLScene(mrmlScene)
      self.__logic.RegisterNodes()
    self.__reportSelector.setMRMLScene(slicer.mrmlScene)
    
  def updateTreeView(self):
    # make the tree view update
    if self.__useNewTreeView == 1:
      self.__markupTreeView.updateTreeView()
    else:
      self.__markupTreeView.sceneModelType = "Displayable"
      nodeTypes = ['vtkMRMLDisplayableHierarchyNode', 'vtkMRMLAnnotationHierarchyNode', 'vtkMRMLAnnotationNode', 'vtkMRMLVolumeNode', 'vtkMRMLReportingReportNode']
      self.__markupTreeView.nodeTypes = nodeTypes
      self.__markupTreeView.listenNodeModifiedEvent = 1      
      self.__markupTreeView.sceneModelType = "Displayable"
      # show these nodes even if they're hidden by being children of hidden hierarchy nodes
      showHiddenNodeTypes = ['vtkMRMLAnnotationNode', 'vtkMRMLVolumeNode', 'vtkMRMLDisplayableHierarchyNode'] 
      self.__markupTreeView.model().showHiddenForTypes = showHiddenNodeTypes
    # set the root to be the current report hierarchy root 
    if self.__rNode == None:
      Helper.Error("updateTreeView: report node is not initialized!")
      self.__markupTreeView.setRootNode(None)
      return
    else:
      # the tree root node has to be a hierarchy node, so get the associated hierarchy node for the active report node
      rootNode = slicer.vtkMRMLHierarchyNode().GetAssociatedHierarchyNode(self.__rNode.GetScene(), self.__rNode.GetID())
      if rootNode:
        self.__markupTreeView.setRootNode(rootNode)
        Helper.Debug("Setting tree view root to be " + rootNode.GetID())
        self.__markupTreeView.expandAll()
      else:
        Helper.Debug("Setting tree view root to be None")
        self.__markupTreeView.setRootNode(None)

  def onAnnotatedVolumeNodeChanged(self):
    Helper.Debug("onAnnotatedVolumeNodeChanged()")

    # get the current volume node
    selectedVolume = self.__volumeSelector.currentNode()

    # do the error checks
    if selectedVolume == None or self.__rNode == None:
      self.__volumeSelector.setCurrentNode(None)
      return

    uids = selectedVolume.GetAttribute('DICOM.instanceUIDs')
    if uids == None:
      Helper.ErrorPopup("Volume \""+selectedVolume.GetName()+"\" was not loaded from DICOM. Only volumes loaded from DICOM data can be annotated in the Reporting module.")
      self.__volumeSelector.setCurrentNode(None)
      return

    nSlices = selectedVolume.GetImageData().GetExtent()[-1]+1
    if nSlices != len(string.split(uids)):
      Helper.ErrorPopup("Volume \""+selectedVolume.GetName()+"\" was loaded from multi-frame DICOM. Multi-frame DICOM is currently not supported by the Reporting module")
      self.__volumeSelector.setCurrentNode(None)
      return

    # volume node is valid!
    self.__vNode = selectedVolume

    # update the report node
    if self.__rNode != None:
      self.__rNode.SetVolumeNodeID(self.__vNode.GetID())
      self.__rNode.SetName('Report for Volume '+self.__vNode.GetName())

    Helper.SetBgFgVolumes(self.__vNode.GetID(), '')
    Helper.RotateToVolumePlanes()

    # go over all label nodes in the scene
    # if there is a label that has the selected volume as associated node, 
    #   initialize label selector to show that label
    volumeNodes = slicer.mrmlScene.GetNodesByClass('vtkMRMLScalarVolumeNode')
    volumeNodes.SetReferenceCount(volumeNodes.GetReferenceCount()-1)
    associatedLabelFound = False
    for i in range(volumeNodes.GetNumberOfItems()):
      vol = volumeNodes.GetItemAsObject(i)
      associatedNodeID = vol.GetAttribute('AssociatedNodeID')
      label = vol.GetAttribute('LabelMap')
      if associatedNodeID == self.__vNode.GetID() and label == '1':
        Helper.SetLabelVolume(vol.GetID())
        associatedLabelFound = True

    # if there is no associated label node, set the selector to none
    if associatedLabelFound == False:
      Helper.SetLabelVolume("")

    orientation = Helper.GetScanOrderSliceName(self.__vNode)
    message = "Slice viewers to be used for markup: "
    for sliceViewer in orientation:
      message = message + sliceViewer
      if orientation.index(sliceViewer) < (len(orientation) - 1 ):
        message = message + ", "
    Helper.Debug(message)
    self.__markupSliceText.text = message

    # take the first one
    self.__parameterNode.SetParameter('acquisitionSliceViewer',orientation[0])

    # print "Calling logic to set up hierarchy"
    self.__logic.InitializeHierarchyForVolume(self.__vNode)
    self.updateTreeView()

  def onSegmentationNodeChanged(self):
    Helper.Debug('onSegmentationNodeChanged()')

    if self.__vNode == None:
      Helper.Error('Should not be possible to select segmentation unless annotated volume is initialized!')
      return

    # get the current segmentation (label) node
    sNode = self.__segmentationSelector.currentNode()
    if sNode == None:
      self.updateWidgets()
      return

    # if it's a new label, it should have/will be added to the report
    # automatically
    image = sNode.GetImageData()
    if image == None:
      Helper.initializeNewLabel(sNode, self.__vNode)
    else:
      # if it's an existing label, we need to check that the geometry matches
      # the annotated label geometry, and if so, add it to the hierarchy
      if Helper.GeometriesMatch(sNode, self.__vNode) == False:
        Helper.ErrorPopup('The geometry of the segmentation label you attempted to select does not match the geometry of the volume being annotated! Please select a different label or create a new one.')
        self.__segmentationSelector.setCurrentNode(None)
        self.updateWidgets()
        return

    # assign the color LUT we use
    dNode = sNode.GetDisplayNode()
    dNode.SetAndObserveColorNodeID(self.__defaultColorNode.GetID())

    sNode.SetAttribute('AssociatedNodeID',self.__vNode.GetID())
    self.__logic.AddNodeToReport(sNode)

    # assign the volume and the selected color to the editor parameter node
    Helper.SetLabelVolume(sNode.GetID())

    # TODO: disable adding new label node to the hierarchy if it was added
    # outside the reporting module

    self.__segmentationSelector.setCurrentNode(sNode)

    self.__editorWidget.setMasterNode(self.__vNode)
    self.__editorWidget.setMergeNode(sNode)

    self.__editorParameterNode.Modified()

    self.updateWidgets()
  
  def onReportNodeChanged(self):
    Helper.Debug("onReportNodeChanged()")

    # cancel the active effect in Editor
    if self.__editorWidget:
      self.__editorWidget.toolsBox.defaultEffect()
    # TODO
    #  -- initialize annotations and markup frames based on the report node
    #  content
    self.__rNode = self.__reportSelector.currentNode()
      
    self.__segmentationSelector.setCurrentNode(None)
    # disable editor frame
    self.onSegmentationNodeChanged()

    if self.__rNode != None:
    
      Helper.Debug("Selected report has changed to " + self.__rNode.GetID())

      if self.__rNode.GetDICOMDatabaseFileName() == "":
        self.__rNode.SetDICOMDatabaseFileName(self.__dbFileName)

      self.__parameterNode.SetParameter('reportID', self.__rNode.GetID())

      # setup the default color node, if not initialized
      if self.__rNode.GetColorNodeID() == "":
        self.__rNode.SetColorNodeID(self.__defaultColorNode.GetID())
        Helper.Debug('Set color node id to '+self.__defaultColorNode.GetID())
      else:
        Helper.Debug('Color node has already been set to '+self.__rNode.GetColorNodeID())

      self.__logic.InitializeHierarchyForReport(self.__rNode)
      self.updateTreeView()
      vID = self.__rNode.GetVolumeNodeID()
      if vID:
        Helper.Debug('Have a volume node id in the report ' + vID + ', setting current volume node selector')
        self.__vNode = slicer.mrmlScene.GetNodeByID(vID)      
        self.__volumeSelector.setCurrentNode(self.__vNode)
      else:
        Helper.Debug('Do not have a volume id in the report, setting current volume node selector to none')
        # set the volume to be none
        self.__vNode = None
        self.__volumeSelector.setCurrentNode(None)

      # hide the markups that go with other report nodes
      self.__logic.HideAnnotationsForOtherReports(self.__rNode)

      # update the GUI annotation name/label/color
      self.updateWidgets()

      # initialize the label used by the EditorWidget
      if self.__editorParameterNode:
        self.__editorParameterNode.SetParameter('label',str(self.__rNode.GetFindingLabel()))

  '''
  Load report and initialize GUI based on .xml report file content
  '''
  def onReportImport(self):

    print('onReportImport here!!!')
    # TODO
    #  -- popup file open dialog to choose the .xml AIM file
    #  -- warn the user if the selected report node is not empty
    #  -- populate the selected report node, initializing annotation template,
    #  content, markup hierarchy and content
    if not self.__importAIMFile:
      Helper.Debug('onReportImport: import file name not specified')
      return

    Helper.Debug('onReportImport')    

    # For now, always create a new report node
    newReport = slicer.modulemrml.vtkMRMLReportingReportNode()

    # always use the default color map
    newReport.SetColorNodeID(self.__defaultColorNode.GetID())

    slicer.mrmlScene.AddNode(newReport)
    self.__reportSelector.setCurrentNode(newReport)
    self.onReportNodeChanged()

    # initialize the report hierarchy
    #  -- assume that report node has been created and is in the selector

    Helper.LoadAIMFile(newReport.GetID(),self.__importAIMFile)

    # update the GUI
    Helper.Debug('onReportImport --> calling onReportNodeChanged()')
    self.onReportNodeChanged()
    
  def onSelectAIMFile(self):
    #  -- popup file dialog prompting output file
    if not self.__importAIMFile:
      fileName = qt.QFileDialog.getOpenFileName(self.parent, "Choose AIM report","/","XML Files (*.xml)")
    else:
      lastDir = self.__importAIMFile[0:string.rfind(self.__importAIMFile,'/')]
      fileName = qt.QFileDialog.getOpenFileName(self.parent, "Choose AIM report",lastDir,"XML Files (*.xml)")

    if fileName == '':
      return
    
    self.__importAIMFile = fileName
    try:
      label = string.split(fileName,'/')[-1]
    except:
      label = fileName
    self.__aimFilePicker.text = label

  '''
  Save report to an xml file
  '''
  def onReportExport(self):
    if self.__rNode == None:
      return

    Helper.Debug('onReportExport')
    
    exportDirectory = self.__exportFolderPicker.directory
    self.__rNode.SetStorageDirectoryName(exportDirectory)

    Helper.Debug('Will export to '+exportDirectory)

    # use the currently selected report
    self.__rNode = self.__reportSelector.currentNode()
    if self.__rNode == None:
      return

    #  -- traverse markup hierarchy and translate
    retval = self.__logic.SaveReportToAIM(self.__rNode)
    if retval == EXIT_FAILURE:
      Helper.Error("Failed to save report to '"+exportDirectory+"'")
    else:
      Helper.Debug("Saved report to '"+exportDirectory+"'")

    # This code is commented out because repeated insertion into the database
    # leads to database lockout (illustrated in Testing/RepeatInsertTest.py).
    # For now, the solution will be to import the created DICOM objects
    # manually.
    # attempt to insert each of the saved items into the database
    #filesToInsert = glob.glob(exportDirectory+'/*')
    #for f in filesToInsert:
    #  Helper.Debug('Inserting '+f+' into DICOM database...')
    #  slicer.dicomDatabase.insert(f)
    #  Helper.Debug('Done')

  def updateWidgetFromParameters(self):
    pn = self.__parameterNode
    if pn == None:
      return
    reportID = pn.GetParameter('reportID')

    if reportID != None:
      self.__rNode = Helper.getNodeByID(reportID)
      # AF: looks like this does not trigger event, why?
      self.__reportSelector.setCurrentNode(self.__rNode)

  def updateParametersFromWidget(self):
    pn = self.__parameterNode
    if pn == None:
      return

    report = self.__reportSelector.currentNode()
  
    if report != None:
      pn.SetParameter('reportID', report.GetID())

  def updateWidgets(self):

    Helper.Debug("updateWidgets()")

    report = self.__rNode
    volume = None
    label = self.__segmentationSelector.currentNode()

    if report != None:
      self.__reportSelector.setCurrentNode(self.__rNode)
      volume = slicer.mrmlScene.GetNodeByID(report.GetVolumeNodeID())
      # TODO: get the label node from volume hieararchy

      if volume != None:
        self.__volumeSelector.setCurrentNode(volume)
        self.__markupFrame.enabled = 1
        self.__annotationsFrame.enabled = 1
        self.__volumeSelector.enabled = 0
      else:
        self.__volumeSelector.enabled = 1
        self.__markupFrame.enabled = 0
        self.__annotationsFrame.enabled = 0

    else:
      self.__volumeSelector.enabled = 0
      self.__markupFrame.enabled = 0
      self.__annotationsFrame.enabled = 0
      
    if not label:
      self.__editorWidget.editLabelMapsFrame.collapsed = True
      self.__editorWidget.editLabelMapsFrame.setEnabled(False)
    else:      
      self.__editorWidget.editLabelMapsFrame.collapsed = False
      self.__editorWidget.editLabelMapsFrame.setEnabled(True)
Пример #3
0
class VisAIReWidget:
  def __init__(self, parent = None):
    if not parent:
      self.parent = slicer.qMRMLWidget()
      self.parent.setLayout(qt.QVBoxLayout())
      self.parent.setMRMLScene(slicer.mrmlScene)
    else:
      self.parent = parent
    self.layout = self.parent.layout()
    if not parent:
      self.setup()
      self.parent.show()

    # TODO: figure out why module/class hierarchy is different
    # between developer builds ans packages
    try:
      # for developer build...
      self.editUtil = EditorLib.EditUtil.EditUtil()
    except AttributeError:
      # for release package...
      self.editUtil = EditorLib.EditUtil()

  def setup(self):
    self.viewMode = 'compare'
    self.compare0 = None
    self.compare1 = None
    self.sidebyside0 = None
    self.sidebyside1 = None

    # Instantiate and connect widgets ...

    # reload button
    # (use this during development, but remove it when delivering
    #  your module to users)
    self.reloadButton = qt.QPushButton("Reload")
    self.reloadButton.toolTip = "Reload this module."
    self.reloadButton.name = "VisAIRe Reload"
    self.layout.addWidget(self.reloadButton)
    self.reloadButton.connect('clicked()', self.onReload)

    # reload and test button
    # (use this during development, but remove it when delivering
    #  your module to users)
    #self.reloadAndTestButton = qt.QPushButton("Reload and Test")
    #self.reloadAndTestButton.toolTip = "Reload this module and then run the self tests."
    #self.layout.addWidget(self.reloadAndTestButton)
    #self.reloadAndTestButton.connect('clicked()', self.onReloadAndTest)

    # entry for the rater name
    label = qt.QLabel('Rater name:')
    self.raterName = qt.QLineEdit()
    self.layout.addWidget(label)
    self.layout.addWidget(self.raterName)

    # Configuration file picker
    label = qt.QLabel('Configuration file:')
    self.configFilePicker = qt.QPushButton('N/A')
    self.configFilePicker.connect('clicked()',self.onConfigFileSelected)
    self.layout.addWidget(label)
    self.layout.addWidget(self.configFilePicker)

    self.makeSnapshots = qt.QPushButton('Make snapshots')
    self.layout.addWidget(self.makeSnapshots)
    self.makeSnapshots.connect('clicked()', self.onMakeSnapshots)

    # Opacity control
    label = qt.QLabel('Foreground/Background opacity:')
    self.opacitySlider = ctk.ctkSliderWidget()
    self.opacitySlider.connect('valueChanged(double)',self.onOpacityChangeRequested)
    self.opacitySlider.minimum = 0.
    self.opacitySlider.maximum = 1.
    self.opacitySlider.decimals = 1
    self.opacitySlider.singleStep = 0.1
    self.layout.addWidget(label)
    self.layout.addWidget(self.opacitySlider)

    #  Button to switch between showing background/foreground volumes
    self.bgfgButton = qt.QPushButton("Switch Background/Foreground")
    self.layout.addWidget(self.bgfgButton)
    self.bgfgButton.connect('clicked()', self.onbgfgButtonPressed)

    # Select between compare view and editing layouts
    groupLabel = qt.QLabel('Review mode:')
    self.viewGroup = qt.QButtonGroup()
    self.compareSelector = qt.QRadioButton('Compare view')
    self.sideBySideSelector = qt.QRadioButton('Side by side')
    self.compareSelector.setChecked(1)
    self.viewGroup.addButton(self.compareSelector,1)
    self.viewGroup.addButton(self.sideBySideSelector,2)
    self.groupWidget = qt.QWidget()
    self.groupLayout = qt.QFormLayout(self.groupWidget)
    self.groupLayout.addRow(self.compareSelector, self.sideBySideSelector)
    self.layout.addWidget(self.groupWidget)
    # step4Layout.addRow(groupLabel, self.viewGroup)

    self.viewGroup.connect('buttonClicked(int)', self.onViewUpdateRequested)

    # setup Editor widget
    editorWidgetParent = slicer.qMRMLWidget()
    editorWidgetParent.setLayout(qt.QVBoxLayout())
    editorWidgetParent.setMRMLScene(slicer.mrmlScene)
    self.editorWidget = EditorWidget(parent=editorWidgetParent,showVolumesFrame=False)
    self.editorWidget.setup()
    self.editorParameterNode = self.editUtil.getParameterNode()
    self.layout.addWidget(editorWidgetParent)

    # Slice control
    #label = qt.QLabel('Slice selector:')
    #self.sliceSlider = ctk.ctkSliderWidget()
    #self.sliceSlider.connect('valueChanged(double)',self.onSliceChangeRequested)
    #self.sliceSlider.minimum = 0.
    #self.sliceSlider.maximum = 1.
    #self.sliceSlider.decimals = 1
    #self.sliceSlider.singleStep = 0.1
    #self.layout.addWidget(label)
    #self.layout.addWidget(self.opacitySlider)

    # Collapsible button to keep the content of the form
    self.evaluationFrame = ctk.ctkCollapsibleButton()
    self.evaluationFrame.text = "Assessment Form"
    self.evaluationFrame.collapsed = 0
    self.evaluationFrameLayout = qt.QFormLayout(self.evaluationFrame)
    self.layout.addWidget(self.evaluationFrame)

    self.formEntries = []
    self.questions = {'Improved compared to non-registered?':'binary','Diagnostic quality?':'binary','Error quantification (if available)':'numeric'}
    self.formEntryMapper = qt.QSignalMapper()
    self.formEntryMapper.connect('mapped(const QString&)', self.entrySelected)

    self.maxFormEntries = 30
    for i in range(self.maxFormEntries):
    # populate the assessment form
      # create a new sub-frame with the questions
      cb = ctk.ctkCollapsibleButton()
      cb.visible = False
      cb.collapsed = True
      self.formEntries.append(cb)
      self.formEntryMapper.setMapping(cb, str(i))
      cb.connect('contentsCollapsed(bool)', self.formEntryMapper, 'map()')

      layout = qt.QFormLayout(cb)
      self.evaluationFrameLayout.addRow(cb)

      for (q,c) in self.questions.items():
        if c == 'binary':
          self.addBinaryEntry(q, layout)
        elif c == 'numeric':
          self.addNumericEntry(q, layout)


    # Save button
    self.doneButton = qt.QPushButton("Save")
    self.doneButton.toolTip = "Click this when done."
    self.layout.addWidget(self.doneButton)
    self.doneButton.connect('clicked(bool)', self.onDoneButtonClicked)

    # Add vertical spacer
    self.layout.addStretch(1)

    # Initialize internal persistent variables
    self.configFile = None
    self.movingVolume = None
    self.perVolumeForms = []
    self.fixedVolumes = []
    self.registeredVolumes = []
    self.transforms = []
    self.caseName = None


    # add custom layout for comparing two pairs of volumes
    compareViewTwoRows ="<layout type=\"vertical\">"
    for i in range(2):
      compareViewTwoRows = compareViewTwoRows+"   <item>\
    <view class=\"vtkMRMLSliceNode\" singletontag=\"Compare"+str(i)+"\">\
    <property name=\"orientation\" action=\"default\">Axial</property>\
    <property name=\"viewlabel\" action=\"default\">"+str(i)+"</property>\
    <property name=\"viewcolor\" action=\"default\">#E17012</property>\
    <property name=\"lightboxrows\" action=\"default\">1</property>\
    <property name=\"lightboxcolumns\" action=\"default\">6</property>\
    <property name=\"lightboxrows\" action=\"relayout\">1</property>\
    <property name=\"lightboxcolumns\" action=\"relayout\">6</property>\
    </view>\
    </item>"
    compareViewTwoRows = compareViewTwoRows+"</layout>"

    sideBySide = "<layout type=\"horizontal\">\
     <item>\
      <view class=\"vtkMRMLSliceNode\" singletontag=\"SideBySide0\">\
       <property name=\"orientation\" action=\"default\">Axial</property>\
       <property name=\"viewlabel\" action=\"default\">Moving</property>\
       <property name=\"viewcolor\" action=\"default\">#F34A33</property>\
      </view>\
     </item>\
     <item>\
      <view class=\"vtkMRMLSliceNode\" singletontag=\"SideBySide1\">\
       <property name=\"orientation\" action=\"default\">Axial</property>\
       <property name=\"viewlabel\" action=\"default\">Reference</property>\
       <property name=\"viewcolor\" action=\"default\">#EDD54C</property>\
      </view>\
     </item>\
    </layout>"
    print(sideBySide)

    layoutNodes = slicer.mrmlScene.GetNodesByClass('vtkMRMLLayoutNode')
    layoutNodes.SetReferenceCount(layoutNodes.GetReferenceCount()-1)
    self.layoutNode = layoutNodes.GetItemAsObject(0)
    self.CompareLayout = 123
    self.ContouringLayout = 124
    self.layoutNode.AddLayoutDescription(self.CompareLayout,compareViewTwoRows)
    self.layoutNode.AddLayoutDescription(self.ContouringLayout,sideBySide)
    self.layoutNode.SetViewArrangement(self.ContouringLayout)
    self.layoutNode.SetViewArrangement(self.CompareLayout)
    sliceCompositeNodes = slicer.mrmlScene.GetNodesByClass('vtkMRMLSliceCompositeNode')
    sliceCompositeNodes.SetReferenceCount(sliceCompositeNodes.GetReferenceCount()-1)
    sliceNodes = slicer.mrmlScene.GetNodesByClass('vtkMRMLSliceNode')
    sliceNodes.SetReferenceCount(sliceNodes.GetReferenceCount()-1)
    for i in range(sliceCompositeNodes.GetNumberOfItems()):
      scn = sliceCompositeNodes.GetItemAsObject(i)
      sn = sliceNodes.GetItemAsObject(i)
      sn.SetUseLabelOutline(1)
      if sn.GetName() == 'Compare0':
        self.compare0 = scn
      if sn.GetName() == 'Compare1':
        self.compare1 = scn
      if sn.GetName() == 'SideBySide0':
        self.sidebyside0 = scn
      if sn.GetName() == 'SideBySide1':
        self.sidebyside1 = scn

  def compositeNodeForWidget(self,name):
    sliceCompositeNodes = slicer.mrmlScene.GetNodesByClass('vtkMRMLSliceCompositeNode')
    sliceCompositeNodes.SetReferenceCount(sliceCompositeNodes.GetReferenceCount()-1)
    sliceNodes = slicer.mrmlScene.GetNodesByClass('vtkMRMLSliceNode')
    sliceNodes.SetReferenceCount(sliceNodes.GetReferenceCount()-1)
    for i in range(sliceCompositeNodes.GetNumberOfItems()):
      scn = sliceCompositeNodes.GetItemAsObject(i)
      sn = sliceNodes.GetItemAsObject(i)
      sn.SetUseLabelOutline(1)
      if sn.GetName() == name:
        return scn
    return None

  def onViewUpdateRequested(self,id):
    if id == 1:
      self.viewMode = 'compare'
    if id == 2:
      self.viewMode = 'sidebyside'
    self.entrySelected('0')

  def onOpacityChangeRequested(self,value):
    if self.viewMode == 'compare':
      viewer0 = self.compare0
      viewer1 = self.compare1
    else:
      viewer0 = self.sidebyside0
      viewer1 = self.sidebyside1

    if viewer0:
      viewer0.SetForegroundOpacity(value)
    if viewer1:
      viewer1.SetForegroundOpacity(value)

  def onbgfgButtonPressed(self):
    if self.viewMode == 'compare':
      viewer0 = self.compare0
      viewer1 = self.compare1
    else:
      viewer0 = self.sidebyside0
      viewer1 = self.sidebyside1

    if viewer0.GetForegroundOpacity() == 1:
      viewer0.SetForegroundOpacity(0)
      viewer1.SetForegroundOpacity(0)
    else:
      viewer0.SetForegroundOpacity(1)
      viewer1.SetForegroundOpacity(1)

  def onMakeSnapshots(self):
    for key in range(len(self.transforms)):
      print('Key :'+str(key))
      if not self.transforms[key]:
        continue
      self.makeSnapshotsForKey(key)

  def makeSnapshotsForKey(self,name,snapshots=True):
      movingID = self.movingVolume.GetID()
      registeredID = self.registeredVolumes[int(name)].GetID()
      fixedID = self.fixedVolumes[int(name)].GetID()

      snapshotsDir = '/Users/fedorov/Temp/RegistrationSnapshots'
      # assume this is full path, and the file name has the format
      #  <CaseID>_<junk>
      caseId = os.path.split(self.configFileName)[-1].split('_')[0]

      redSliceCompositeNode = self.compositeNodeForWidget('Red')

      redSliceCompositeNode.SetForegroundOpacity(0)

      self.setupLightbox(fixedID,movingID)
      if snapshots:
        snapshotName = os.path.join(snapshotsDir,caseId+'_'+str(name)+'_fixed.png')
        self.makeSnapshot('Red',snapshotName)

      self.setupLightbox(registeredID,movingID)
      if snapshots:
        snapshotName = os.path.join(snapshotsDir,caseId+'_'+str(name)+'_registered.png')
        self.makeSnapshot('Red',snapshotName)

      # make snapshots of the moving image for the first one
      # (same for all keys)
      if str(name) == '0':
        redSliceCompositeNode.SetForegroundOpacity(1)
        if snapshots:
          snapshotName = os.path.join(snapshotsDir,caseId+'_moving.png')
          self.makeSnapshot('Red',snapshotName)

  def makeSnapshot(self, widgetName, snapshotName):
      w = slicer.app.layoutManager().sliceWidget(widgetName)
      if w:
        qt.QPixmap().grabWidget(w).toImage().save(snapshotName)

  def setupSliceWidget(self,swName):
      w = slicer.app.layoutManager().sliceWidget(swName)
      w.fitSliceToBackground()
      sn = self.compositeNodeForWidget(swName)
      sn.SetForegroundOpacity(0)
      n = w.sliceLogic().GetSliceNode()
      fov = n.GetFieldOfView()
      n.SetFieldOfView(fov[0]/2.,fov[1]/2.,fov[2]/2.)

  def entrySelected(self, name):

    # prepare fixed volume label, if it is not available
    if self.movingVolumeSeg and not self.fixedVolumesSegmentations[int(name)] and self.transforms[int(name)]:
      tfm = self.transforms[int(name)]
      print('Resampled segmentation is missing, but will resample with '+tfm.GetID())

      resample = slicer.modules.brainsresample
      volumesLogic = slicer.modules.volumes.logic()
      labelName = self.fixedVolumes[int(name)].GetName()+'-label'
      self.fixedVolumesSegmentations[int(name)] = volumesLogic.CreateAndAddLabelVolume(slicer.mrmlScene, self.fixedVolumes[int(name)], labelName)
      parameters = {}
      parameters['inputVolume'] = self.movingVolumeSeg.GetID()
      parameters['referenceVolume'] = self.fixedVolumes[int(name)].GetID()
      parameters['warpTransform'] = self.transforms[int(name)].GetID()
      parameters['outputVolume'] = self.fixedVolumesSegmentations[int(name)]
      parameters['pixelType'] = 'short'
      parameters['interpolationMode'] = 'NearestNeighbor'
      slicer.cli.run(resample, None, parameters, wait_for_completion = True)

      # initialize the file name for the segmentation
      storageNode = self.fixedVolumesSegmentations[int(name)].GetStorageNode()
      fixedVolumeIdStr = str(self.fixedVolumeIds[int(name)])
      segFileName = os.path.join(os.path.split(self.config.get('MovingData','Segmentation'))[0],fixedVolumeIdStr+'-label.nrrd')
      storageNode.SetFileName(segFileName)
      storageNode.SetWriteFileFormat('.nrrd')

      # update the config file to keep reference to the newly created
      # segmentation label
      self.config.set('FixedData','Segmentation'+fixedVolumeIdStr,segFileName)

      # setup the Editor
      self.editorWidget.setMasterNode(self.fixedVolumes[int(name)])
      self.editorWidget.setMergeNode(self.fixedVolumesSegmentations[int(name)])
      self.editorParameterNode.Modified()

    if self.viewMode == 'compare':
      self.layoutNode.SetViewArrangement(self.CompareLayout)
      viewer0 = self.compare0
      viewer1 = self.compare1
    else:
      self.layoutNode.SetViewArrangement(self.ContouringLayout)
      viewer0 = self.sidebyside0
      viewer1 = self.sidebyside1

    entry = self.formEntries[int(name)]
    if entry.collapsed == True:
      return

    print 'Entry selected: ',name
    for i in range(len(self.fixedVolumes)):
      #self.formEntries[i].blockSignals(True)
      if i == int(name):
        self.formEntries[i].collapsed = False
        continue
      else:
        self.formEntries[i].collapsed = True
      #self.formEntries[i].blockSignals(False)
    viewer0.SetLinkedControl(False)
    viewer1.SetLinkedControl(False)

    v0fgID = self.movingVolume.GetID()
    v1fgID = self.registeredVolumes[int(name)].GetID()
    bgID = self.fixedVolumes[int(name)].GetID()
    print('Will be setting ids: '+v0fgID+','+v1fgID+','+bgID)

    viewer0.SetForegroundVolumeID(v0fgID)
    if self.movingVolumeSeg:
      viewer0.SetLabelVolumeID(self.movingVolumeSeg.GetID())

    viewer1.SetForegroundVolumeID(v1fgID)
    # if segmentation is available for the registered node, display it
    if self.fixedVolumesSegmentations[int(name)]:
      viewer1.SetLabelVolumeID(self.fixedVolumesSegmentations[int(name)].GetID())
    # otherwise, if the transform is available, resample the moving volume
    # segmentation, populate the entry in the list of segmentations and
    # initialize the view


    viewer0.SetLinkedControl(True)
    viewer1.SetLinkedControl(True)
    viewer0.SetBackgroundVolumeID(bgID)
    viewer1.SetBackgroundVolumeID(bgID)

  def setupLightbox(self,fixedID,movingID):
    lm = slicer.app.layoutManager()
    lm.setLayout(6) # Red
    w = lm.sliceWidget('Red')
    cn = self.compositeNodeForWidget('Red')
    cn.SetForegroundVolumeID(movingID)
    cn.SetBackgroundVolumeID(fixedID)
    slicer.app.processEvents()
    sc = w.sliceController()
    sc.setLightbox(3,6)
    w.fitSliceToBackground()
    slider = sc.children()[1].children()[-1]
    n = w.sliceLogic().GetSliceNode()
    fov = n.GetFieldOfView()
    # zoom into the prostate region
    n.SetFieldOfView(fov[0]/2.5,fov[1]/2.5,fov[2])
    sc.setSliceOffsetValue(slider.minimum)

  def initializeViews(self, name):
    if self.viewMode == 'compare':
      self.layoutNode.SetViewArrangement(self.CompareLayout)
      viewer0 = self.compare0
      viewer1 = self.compare1
    else:
      self.layoutNode.SetViewArrangement(self.ContouringLayout)
      viewer0 = self.sidebyside0
      viewer1 = self.sidebyside1

    viewer0.SetLinkedControl(False)
    viewer1.SetLinkedControl(False)

    v0fgID = self.movingVolume.GetID()
    v1fgID = self.registeredVolumes[int(name)].GetID()
    bgID = self.fixedVolumes[int(name)].GetID()
    print('Will be setting ids: '+v0fgID+','+v1fgID+','+bgID)

    viewer0.SetForegroundVolumeID(v0fgID)
    if self.movingVolumeSeg:
      viewer0.SetLabelVolumeID(self.movingVolumeSeg.GetID())

    viewer1.SetForegroundVolumeID(v1fgID)
    # if segmentation is available for the registered node, display it
    if self.fixedVolumesSegmentations[int(name)]:
      viewer1.SetLabelVolumeID(self.fixedVolumesSegmentations[int(name)].GetID())
    # otherwise, if the transform is available, resample the moving volume
    # segmentation, populate the entry in the list of segmentations and
    # initialize the view


    viewer0.SetLinkedControl(True)
    viewer1.SetLinkedControl(True)
    viewer0.SetBackgroundVolumeID(bgID)
    viewer1.SetBackgroundVolumeID(bgID)


  def onConfigFileSelected(self):
    if not self.configFile:
      fileName = qt.QFileDialog.getOpenFileName(self.parent, "Choose assessment for configuration file","/","Conf Files (*.ini)")
    else:
      lastDir = self.configFile[0:string.rfind(self.configFile,'/')]
      fileName = qt.QFileDialog.getOpenFileName(self.parent, "Choose assessment for configuration file",lastDir,"Conf Files (*.ini)")

    if fileName == '':
      return

    self.configFile = fileName
    try:
      label = string.split(fileName,'/')[-1]
    except:
      label = fileName
    self.configFilePicker.text = label

    self.initFromFile(fileName)

  def initFromFile(self, fileName):
    # parse config file and load all of the volumes referenced
    slicer.mrmlScene.Clear(0)
    self.clearForm()
    self.fixedVolumes = []
    self.fixedVolumeIds = []
    self.registeredVolumes = []
    self.movingVolume = None
    self.movingVolumeSeg = None

    self.config = config.SafeConfigParser()
    cf = self.config
    cf.optionxform = str
    cf.read(fileName)
    self.configFileName = fileName

    assert cf.has_section('Info')
    assert cf.has_section('MovingData')
    assert cf.has_section('FixedData')
    assert cf.has_section('RegisteredData')

    # there should only be one moving image
    self.movingVolume = slicer.util.loadVolume(cf.get('MovingData','Image'),returnNode=True)[1]
    dn = self.movingVolume.GetDisplayNode()
    mwl = [dn.GetWindow(), dn.GetLevel()]

    # (optional) segmentation of the moving image
    print('Set moving volume seg')
    try:
      self.movingVolumeSeg = slicer.util.loadLabelVolume(cf.get('MovingData','Segmentation'),{},returnNode=True)[1]
      self.movingVolumeSeg.SetAttribute('LabelMap','1')
      self.movingVolumeSeg.GetDisplayNode().SetAndObserveColorNodeID('vtkMRMLColorTableNodeFileGenericAnatomyColors.txt')
      print('Setup color node: '+self.movingVolumeSeg.GetDisplayNode().GetColorNodeID())
    except:
      print(' ... to None')
      self.movingVolumeSeg = None

    # fixedVolumes: Slicer volume nodes corresponding to fixed images from the
    #   config file
    # fixedVolumeIds: Each volume appears as "Image<id>" in the config file;
    #   this list stores the ids assigned to the volumes
    # registeredVolumes: results of registration
    # transforms: transformations mapping moving volume to the corresponding
    #   fixed volume
    # fixedVolumesSegmentations: masks of a structure contoured in each of the
    #   fixed volumes
    # movingFiducials: fiducial points corresponding to image landmarks
    # fixedFiducials: fiducial points corresponding to the same landmarks in
    #   the fixed images

    # and an arbitrary number of fixed images
    fixedImageFiles = cf.options('FixedData')
    print(str(fixedImageFiles))
    for fi in fixedImageFiles:
      if re.match('Image\d+',fi):
        self.fixedVolumes.append(slicer.util.loadVolume(cf.get('FixedData',fi),returnNode=True)[1])
        fixedDisplNode = self.fixedVolumes[-1].GetDisplayNode()
        fixedDisplNode.SetAutoWindowLevel(0)
        fixedDisplNode.SetWindow(mwl[0])
        fixedDisplNode.SetLevel(mwl[1])
        self.fixedVolumeIds.append(fi.split('Image')[1])
    print('Fixed volumes: '+str(self.fixedVolumes))

    # (optional) segmentations of the structure in the fixed images
    self.registeredVolumes = [None] * len(self.fixedVolumes)
    self.fixedVolumesSegmentations = [None] * len(self.fixedVolumes)
    self.transforms = [None] * len(self.fixedVolumes)
    for fvId in range(len(self.fixedVolumes)):
      imageId = 'Image'+self.fixedVolumeIds[fvId]
      segId = 'Segmentation'+self.fixedVolumeIds[fvId]
      tfmId = 'Transform'+self.fixedVolumeIds[fvId]
      try:
        self.registeredVolumes[fvId] = slicer.util.loadVolume(cf.get('RegisteredData',imageId),{},returnNode=True)[1]
        registeredDisplNode = self.registeredVolumes[fvId].GetDisplayNode()
        registeredDisplNode.SetAutoWindowLevel(0)
        registeredDisplNode.SetWindow(mwl[0])
        registeredDisplNode.SetLevel(mwl[1])
      except:
        print('Failed to read RegisteredData/'+imageId)
        pass
      try:
        self.fixedVolumesSegmentations[fvId] = slicer.util.loadLabelVolume(cf.get('FixedData',segId),{},returnNode=True)[1]
      except:
        print('Failed to read FixedData/'+segId)
        pass
      #try:
      self.transforms[fvId] = slicer.util.loadTransform(cf.get('RegisteredData',tfmId),returnNode=True)[1]
      #except:
      #  print('Failed to read RegisteredData/'+tfmId)
      #  pass

    print('Number of fixed images: '+str(self.registeredVolumes))
    print('Number of transformations: '+str(self.transforms))
    print('Number of fixed image segmentations: '+str(self.fixedVolumesSegmentations))

    assert len(self.fixedVolumes) == len(self.registeredVolumes)

    self.caseName = cf.get('Info','CaseName')
    self.evaluationFrame.text = "Assessment Form for "+self.caseName

    # populate the assessment form
    for fv in range(len(self.fixedVolumes)):
      self.formEntries[fv].visible = True
      self.formEntries[fv].text = self.fixedVolumes[fv].GetName()

    # self.entrySelected('0')

  def clearForm(self):
    for i in range(self.maxFormEntries):
      self.formEntries[i].visible = False

  def onDoneButtonClicked(self):
    # save the config file
    self.config.write(open(self.configFileName,'w'))

    return

    path = self.configFile[0:string.rfind(self.configFile,'/')]
    reportName = path+'/'+self.caseName+'-'+self.raterName.text+'-report.log'
    report = open(reportName,'w')
    report.write(self.configFile+'\n')
    for i in range(len(self.fixedVolumes)):
      report.write(self.fixedVolumes[i].GetName()+';')
      item = 2
      print 'num children: ',len(self.formEntries[i].children())
      for (q,c) in self.questions.items():
        report.write(q+';')
        widget = self.formEntries[i].children()[item]
        print widget
        if c == 'binary':
          checked = str(int(widget.checked))
          report.write(str(checked)+';')
        elif c == 'numeric':
          error = str(widget.text)
          report.write(error+';')
        item = item+2
      report.write('\n')
    report.close()

  def addBinaryEntry(self,question,layout):
    self.questions[question] = 'binary'
    label = qt.QLabel(question)
    item = qt.QCheckBox()
    layout.addRow(label,item)
    #self.formEntries.append(item)

  def addNumericEntry(self,question,layout):
    self.questions[question] = 'numeric'
    label = qt.QLabel(question)
    item = qt.QLineEdit()
    layout.addRow(label,item)
    #self.formEntries.append(item)

  def onReload(self,moduleName="VisAIRe"):
    """Generic reload method for any scripted module.
    ModuleWizard will subsitute correct default moduleName.
    """
    import imp, sys, os, slicer

    widgetName = moduleName + "Widget"

    # reload the source code
    # - set source file path
    # - load the module to the global space
    filePath = eval('slicer.modules.%s.path' % moduleName.lower())
    p = os.path.dirname(filePath)
    if not sys.path.__contains__(p):
      sys.path.insert(0,p)
    fp = open(filePath, "r")
    globals()[moduleName] = imp.load_module(
        moduleName, fp, filePath, ('.py', 'r', imp.PY_SOURCE))
    fp.close()

    # rebuild the widget
    # - find and hide the existing widget
    # - create a new widget in the existing parent
    parent = slicer.util.findChildren(name='%s Reload' % moduleName)[0].parent()
    for child in parent.children():
      try:
        child.hide()
      except AttributeError:
        pass
    # Remove spacer items
    item = parent.layout().itemAt(0)
    while item:
      parent.layout().removeItem(item)
      item = parent.layout().itemAt(0)

    # delete the old widget instance
    '''
    widgetName = moduleName+'Widget'
    if hasattr(globals()['slicer'].modules, widgetName):
      getattr(globals()['slicer'].modules, widgetName).cleanup()
    '''

    # create new widget inside existing parent
    globals()[widgetName.lower()] = eval(
        'globals()["%s"].%s(parent)' % (moduleName, widgetName))
    globals()[widgetName.lower()].setup()

  def onReloadAndTest(self,moduleName="VisAIRe"):
    self.onReload()
    evalString = 'globals()["%s"].%sTest()' % (moduleName, moduleName)
    tester = eval(evalString)
    tester.runTest()
Пример #4
0
class SegmentationStep(GBMWizardStep):
    def __init__(self, stepid):

        self.initialize(stepid)
        self.setName('5. Threshold')

        self.__vrDisplayNode = None
        self.__threshold = [-1, -1]

        # Initialize volume rendering.
        self.__vrLogic = slicer.modules.volumerendering.logic()
        self.__vrOpacityMap = None
        self.__vrColorMap = None

        self.__thresholdedLabelNode = None
        self.__roiVolume = None
        self.__visualizedVolume = None

        self.__parent = super(SegmentationStep, self)

    def createUserInterface(self):
        """ This UI takes advantage of a pre-built slicer thresholding widget.
        """

        self.__layout = self.__parent.createUserInterface()

        step_label = qt.QLabel(
            """Automatic segmentation with deep learning is not available in this demo, due to lack of computing resources. You may however create a segmentation using 3D Slicer's editor module in this step."""
        )
        step_label.setWordWrap(True)
        self.__informationGroupBox = qt.QGroupBox()
        self.__informationGroupBox.setTitle('Information')
        self.__informationGroupBoxLayout = qt.QFormLayout(
            self.__informationGroupBox)
        self.__informationGroupBoxLayout.addRow(step_label)
        self.__layout.addRow(self.__informationGroupBox)

        editorWidgetParent = slicer.qMRMLWidget()
        editorWidgetParent.setLayout(qt.QVBoxLayout())
        editorWidgetParent.setMRMLScene(slicer.mrmlScene)
        self.EditorWidget = EditorWidget(parent=editorWidgetParent)
        self.EditorWidget.setup()
        self.__layout.addRow(editorWidgetParent)

        # self.__thresholdGroupBox = qt.QGroupBox()
        # self.__thresholdGroupBox.setTitle('Threshold Range')
        # self.__thresholdGroupBoxLayout = qt.QFormLayout(self.__thresholdGroupBox)
        # threshLabel = qt.QLabel('Select Intensity Range:')
        # threshLabel.alignment = 4

        # self.__threshRange = slicer.qMRMLRangeWidget()
        # self.__threshRange.decimals = 0
        # self.__threshRange.singleStep = 1

        # self.__thresholdGroupBoxLayout.addRow(threshLabel)
        # self.__thresholdGroupBoxLayout.addRow(self.__threshRange)
        # self.__layout.addRow(self.__thresholdGroupBox)

        # self.__threshRange.connect('valuesChanged(double,double)', self.onThresholdChanged)
        qt.QTimer.singleShot(0, self.killButton)

    def onThresholdChanged(self):
        """ Upon changing the slider (or intializing this step), this method
            updates the volume rendering node and label volume accordingly.
        """

        range0 = self.__threshRange.minimumValue
        range1 = self.__threshRange.maximumValue

        # Use vtk to threshold the label volume.
        if self.__roiVolume != None:
            thresh = vtk.vtkImageThreshold()
            if vtk.VTK_MAJOR_VERSION <= 5:
                thresh.SetInput(self.__roiVolume.GetImageData())
            else:
                thresh.SetInputData(self.__roiVolume.GetImageData())
            thresh.ThresholdBetween(range0, range1)
            thresh.SetInValue(1)
            thresh.SetOutValue(0)
            thresh.ReplaceOutOn()
            thresh.ReplaceInOn()
            thresh.Update()

            self.__thresholdedLabelNode.SetAndObserveImageData(
                thresh.GetOutput())

    def killButton(self):
        # ctk creates a useless final page button. This method gets rid of it.
        bl = slicer.util.findChildren(text='ReviewStep')
        if len(bl):
            bl[0].hide()

    def validate(self, desiredBranchId):
        # For now, no validation required.
        self.__parent.validationSucceeded(desiredBranchId)

    def onEntry(self, comingFrom, transitionType):
        """ This method removes and adds nodes necessary to for a segementation
            display, intializes color and opacity maps, and calls the main 
            thresholding function for the first time.
        """

        super(SegmentationStep, self).onEntry(comingFrom, transitionType)

        pNode = self.parameterNode()

        self.EditorWidget.setMergeNode(self.__thresholdedLabelNode)
        self.EditorWidget.volumes.collapsed = True
        self.EditorWidget.editLabelMapsFrame.collapsed = False
        try:
            self.EditorWidget.segmentEditorLabel.hide()
            self.EditorWidget.infoIconLabel.hide()
        except:
            pass

        # pNode = self.parameterNode()

        # # What if someone goes to the Volume Rendering module, creates a new VRNode,
        # # and then returns? Need some way to check if self.__vrNode is currently
        # # being viewed.

        # self.__vrDisplayNode = Helper.getNodeByID(pNode.GetParameter('vrDisplayNodeID'))
        # self.updateWidgetFromParameters(pNode)

        # # Retrieves necessary nodes.
        # self.__roiVolume = Helper.getNodeByID(pNode.GetParameter('croppedVolumeID'))
        # self.__thresholdedLabelNode = Helper.getNodeByID(pNode.GetParameter('thresholdedLabelID'))
        # self.__nonThresholdedLabelNode = Helper.getNodeByID(pNode.GetParameter('nonThresholdedLabelID'))

        # # self.InitVRDisplayNode()

        # # Adds segementation label volume.
        # Helper.SetLabelVolume(self.__thresholdedLabelNode.GetID())

        # threshRange = [self.__threshRange.minimumValue, self.__threshRange.maximumValue]

        # # Segments the entire vtk model. I assume there's a more concise way
        # # to do it than thresholding over its entire intensity range, so TODO
        # range0 = self.__threshRange.minimum
        # range1 = self.__threshRange.maximum
        # thresh = vtk.vtkImageThreshold()
        # if vtk.VTK_MAJOR_VERSION <= 5:
        #   thresh.SetInput(self.__roiVolume.GetImageData())
        # else:
        #   thresh.SetInputData(self.__roiVolume.GetImageData())
        # thresh.ThresholdBetween(range0, range1)
        # thresh.SetInValue(1)
        # thresh.SetOutValue(0)
        # thresh.ReplaceOutOn()
        # thresh.ReplaceInOn()
        # thresh.Update()
        # self.__nonThresholdedLabelNode.SetAndObserveImageData(thresh.GetOutput())

        # # Adjusts threshold information.
        # self.onThresholdChanged()

        pNode.SetParameter('currentStep', self.stepid)

        qt.QTimer.singleShot(0, self.killButton)

    def updateWidgetFromParameters(self, pNode):
        """ Intializes the threshold and label volume established in the previous step.
        """

        pass

        # if pNode.GetParameter('followupVolumeID') == None or pNode.GetParameter('followupVolumeID') == '':
        #     Helper.SetBgFgVolumes(pNode.GetParameter('baselineVolumeID'), '')
        #     self.__visualizedVolume = Helper.getNodeByID(pNode.GetParameter('baselineVolumeID'))
        # else:
        #     Helper.SetBgFgVolumes(pNode.GetParameter('subtractVolumeID'), pNode.GetParameter('followupVolumeID'))
        #     self.__visualizedVolume = Helper.getNodeByID(pNode.GetParameter('subtractVolumeID'))

        # thresholdRange = [float(pNode.GetParameter('intensityThreshRangeMin')), float(pNode.GetParameter('intensityThreshRangeMax'))]

        # if thresholdRange != '':
        #     self.__threshRange.maximum = thresholdRange[1]
        #     self.__threshRange.minimum = thresholdRange[0]
        #     self.__threshRange.maximumValue = thresholdRange[1]
        #     self.__threshRange.minimumValue = thresholdRange[0]
        # else:
        #     Helper.Error('Unexpected parameter values! Error code CT-S03-TNA. Please report')

        # labelID = pNode.GetParameter('thresholdedLabelID')
        # self.__thresholdedLabelNode = Helper.getNodeByID(labelID)

    def onExit(self, goingTo, transitionType):
        pNode = self.parameterNode()

        # if self.__vrDisplayNode != None:
        #   # self.__vrDisplayNode.VisibilityOff()
        #   pNode.SetParameter('vrDisplayNodeID', self.__vrDisplayNode.GetID())

        # roiRange = self.__threshRange
        # pNode.SetParameter('intensityThreshRangeMin', str(roiRange.minimumValue))
        # pNode.SetParameter('intensityThreshRangeMax', str(roiRange.maximumValue))
        # pNode.SetParameter('vrThreshRangeMin', str(roiRange.minimumValue))
        # pNode.SetParameter('vrThreshRangeMax', str(roiRange.maximumValue))

        super(GBMWizardStep, self).onExit(goingTo, transitionType)

    def InitVRDisplayNode(self):
        """ This method calls a series of steps necessary to initailizing a volume 
            rendering node with an ROI.
        """
        if self.__vrDisplayNode == None or self.__vrDisplayNode == '':
            pNode = self.parameterNode()
            self.__vrDisplayNode = self.__vrLogic.CreateVolumeRenderingDisplayNode(
            )
            slicer.mrmlScene.AddNode(self.__vrDisplayNode)
            # Documentation on UnRegister is scant so far.
            self.__vrDisplayNode.UnRegister(self.__vrLogic)

            Helper.InitVRDisplayNode(self.__vrDisplayNode,
                                     self.__roiVolume.GetID(), '')
            self.__roiVolume.AddAndObserveDisplayNodeID(
                self.__vrDisplayNode.GetID())
        else:
            self.__vrDisplayNode.SetAndObserveVolumeNodeID(
                self.__roiVolume.GetID())

        # This is a bit messy.
        viewNode = slicer.util.getNode('vtkMRMLViewNode1')

        self.__vrDisplayNode.AddViewNodeID(viewNode.GetID())

        self.__vrLogic.CopyDisplayToVolumeRenderingDisplayNode(
            self.__vrDisplayNode)

        self.__vrOpacityMap = self.__vrDisplayNode.GetVolumePropertyNode(
        ).GetVolumeProperty().GetScalarOpacity()
        self.__vrColorMap = self.__vrDisplayNode.GetVolumePropertyNode(
        ).GetVolumeProperty().GetRGBTransferFunction()

        vrRange = self.__visualizedVolume.GetImageData().GetScalarRange()

        # Renders in yellow, like the label map in the next steps.
        self.__vrColorMap.RemoveAllPoints()
        self.__vrColorMap.AddRGBPoint(vrRange[0], 0.8, 0.8, 0)
        self.__vrColorMap.AddRGBPoint(vrRange[1], 0.8, 0.8, 0)
Пример #5
0
class ReviewStep(GBMWizardStep):
    def __init__(self, stepid):

        self.initialize(stepid)
        self.setName('6. Review')

        self.__vrDisplayNode = None
        self.__threshold = [-1, -1]

        # initialize VR stuff
        self.__vrLogic = slicer.modules.volumerendering.logic()
        self.__vrOpacityMap = None
        self.__vrColorMap = None

        self.__thresholdedLabelNode = None
        self.__roiVolume = None

        self.__parent = super(ReviewStep, self)
        self.__RestartActivated = False

    def createUserInterface(self):

        self.__layout = self.__parent.createUserInterface()

        step_label = qt.QLabel(
            """Review your segmentation. Use the 3D Visualization slider to see your segmentation in context with your image. Use the Editor panel to apply spot edits to your segmentation. If you would like to start over, see the Restart box below"""
        )
        step_label.setWordWrap(True)
        self.__primaryGroupBox = qt.QGroupBox()
        self.__primaryGroupBox.setTitle('Information')
        self.__primaryGroupBoxLayout = qt.QFormLayout(self.__primaryGroupBox)
        self.__primaryGroupBoxLayout.addRow(step_label)
        self.__layout.addRow(self.__primaryGroupBox)

        # self.__threshRange = slicer.qMRMLRangeWidget()
        # self.__threshRange.decimals = 0
        # self.__threshRange.singleStep = 1
        # self.__threshRange.connect('valuesChanged(double,double)', self.onThresholdChanged)
        # qt.QTimer.singleShot(0, self.killButton)

        # ThreshGroupBox = qt.QGroupBox()
        # ThreshGroupBox.setTitle('3D Visualization Intensity Threshold')
        # ThreshGroupBoxLayout = qt.QFormLayout(ThreshGroupBox)
        # ThreshGroupBoxLayout.addRow(self.__threshRange)
        # self.__layout.addRow(ThreshGroupBox)

        editorWidgetParent = slicer.qMRMLWidget()
        editorWidgetParent.setLayout(qt.QVBoxLayout())
        editorWidgetParent.setMRMLScene(slicer.mrmlScene)
        self.EditorWidget = EditorWidget(parent=editorWidgetParent)
        self.EditorWidget.setup()
        self.__layout.addRow(editorWidgetParent)

        RestartGroupBox = qt.QGroupBox()
        RestartGroupBox.setTitle('Restart')
        RestartGroupBoxLayout = qt.QFormLayout(RestartGroupBox)

        self.__RestartButton = qt.QPushButton('Return to Step 1')
        RestartGroupBoxLayout.addRow(self.__RestartButton)

        self.__RemoveRegisteredImage = qt.QCheckBox()
        self.__RemoveRegisteredImage.checked = True
        self.__RemoveRegisteredImage.setToolTip(
            "Delete your registered images.")
        RestartGroupBoxLayout.addRow("Delete Registered images: ",
                                     self.__RemoveRegisteredImage)

        self.__RemoveNormalizedImages = qt.QCheckBox()
        self.__RemoveNormalizedImages.checked = True
        self.__RemoveNormalizedImages.setToolTip(
            "Delete your normalized images.")
        RestartGroupBoxLayout.addRow("Delete Normalized images: ",
                                     self.__RemoveNormalizedImages)

        self.__RemoveSubtractionMap = qt.QCheckBox()
        self.__RemoveSubtractionMap.checked = True
        self.__RemoveSubtractionMap.setToolTip("Delete your subtraction map.")
        RestartGroupBoxLayout.addRow("Delete Subtraction map: ",
                                     self.__RemoveSubtractionMap)

        self.__RemoveCroppedMap = qt.QCheckBox()
        self.__RemoveCroppedMap.checked = True
        self.__RemoveCroppedMap.setToolTip(
            "Delete the cropped version of your input volume.")
        RestartGroupBoxLayout.addRow("Delete Cropped Volume: ",
                                     self.__RemoveCroppedMap)

        self.__RemoveROI = qt.QCheckBox()
        self.__RemoveROI.checked = False
        self.__RemoveROI.setToolTip(
            "Delete the ROI you made with your markup points.")
        RestartGroupBoxLayout.addRow("Delete Full ROI: ", self.__RemoveROI)

        self.__RemoveThresholdedROI = qt.QCheckBox()
        self.__RemoveThresholdedROI.checked = False
        self.__RemoveThresholdedROI.setToolTip(
            "Delete the intensity-thresholded version of your ROI.")
        RestartGroupBoxLayout.addRow("Delete Thresholded ROI: ",
                                     self.__RemoveThresholdedROI)

        self.__RemoveMarkups = qt.QCheckBox()
        self.__RemoveMarkups.checked = True
        self.__RemoveMarkups.setToolTip(
            "Delete the markup points you used to create your 3D ROI.")
        RestartGroupBoxLayout.addRow("Delete Markup Points: ",
                                     self.__RemoveMarkups)

        self.__RemoveModels = qt.QCheckBox()
        self.__RemoveModels.checked = True
        self.__RemoveModels.setToolTip(
            "Delete the 3D model you created from your markup points.")
        RestartGroupBoxLayout.addRow("Delete 3D Model: ", self.__RemoveModels)

        self.__RestartButton.connect('clicked()', self.Restart)
        self.__RestartActivated = True

        self.__layout.addRow(RestartGroupBox)

    def hideUnwantedEditorUIElements(self):
        """ We import the Editor module wholesale, which is useful, but it means
            we have to manually hide parts we don't want after the fact..
            If we could somehow import the segmentations module instead, that
            might be better. On the other hand, first-time users often don't know
            how to use the segmentations module.
        """

        self.EditorWidget.setMergeNode(self.__thresholdedLabelNode)
        self.EditorWidget.volumes.collapsed = True
        self.EditorWidget.editLabelMapsFrame.collapsed = False
        try:
            self.EditorWidget.segmentEditorLabel.hide()
            self.EditorWidget.infoIconLabel.hide()
        except:
            pass

    def Restart(self):

        # Unclick any selected editor tools..
        self.__DefaultToolButton.click()

        pNode = self.parameterNode()

        slicer.mrmlScene.RemoveNode(
            Helper.getNodeByID(pNode.GetParameter('clippingModelNodeID')))
        slicer.mrmlScene.RemoveNode(
            Helper.getNodeByID(pNode.GetParameter('clippingMarkupNodeID')))
        slicer.mrmlScene.RemoveNode(
            Helper.getNodeByID(pNode.GetParameter('vrDisplayNodeID')))

        if self.__RemoveRegisteredImage.checked:
            slicer.mrmlScene.RemoveNode(
                Helper.getNodeByID(pNode.GetParameter('registrationVolumeID')))

        if self.__RemoveNormalizedImages.checked:
            for node in [
                    pNode.GetParameter('baselineNormalizeVolumeID'),
                    pNode.GetParameter('followupNormalizeVolumeID')
            ]:
                if node != pNode.GetParameter(
                        'baselineVolumeID') and node != pNode.GetParameter(
                            'followupVolumeID'):
                    slicer.mrmlScene.RemoveNode(Helper.getNodeByID(node))

        if self.__RemoveSubtractionMap.checked:
            slicer.mrmlScene.RemoveNode(
                Helper.getNodeByID(pNode.GetParameter('subtractVolumeID')))

        if self.__RemoveCroppedMap.checked:
            slicer.mrmlScene.RemoveNode(
                Helper.getNodeByID(pNode.GetParameter('croppedVolumeID')))

        if self.__RemoveROI.checked:
            slicer.mrmlScene.RemoveNode(
                Helper.getNodeByID(
                    pNode.GetParameter('nonThresholdedLabelID')))

        if self.__RemoveThresholdedROI.checked:
            slicer.mrmlScene.RemoveNode(
                Helper.getNodeByID(pNode.GetParameter('thresholdedLabelID')))

        if self.__RemoveMarkups.checked:
            slicer.mrmlScene.RemoveNode(
                Helper.getNodeByID(pNode.GetParameter('clippingMarkupNodeID')))

        if self.__RemoveModels.checked:
            slicer.mrmlScene.RemoveNode(
                Helper.getNodeByID(pNode.GetParameter('clippingModelNodeID')))

        pNode.SetParameter('baselineVolumeID', '')
        pNode.SetParameter('followupVolumeID', '')
        pNode.SetParameter('originalBaselineVolumeID', '')
        pNode.SetParameter('originalFollowupVolumeID', '')

        pNode.SetParameter('registrationVolumeID', '')

        pNode.SetParameter('baselineNormalizeVolumeID', '')
        pNode.SetParameter('followupNormalizeVolumeID', '')
        pNode.SetParameter('subtractVolumeID', '')

        pNode.SetParameter('clippingMarkupNodeID', '')
        pNode.SetParameter('clippingModelNodeID', '')
        pNode.SetParameter('outputList', '')
        pNode.SetParameter('markupList', '')
        pNode.SetParameter('modelList', '')

        pNode.SetParameter('thresholdedLabelID', '')
        pNode.SetParameter('croppedVolumeID', '')
        pNode.SetParameter('nonThresholdedLabelID', '')

        pNode.SetParameter('roiNodeID', '')
        pNode.SetParameter('roiTransformID', '')

        pNode.SetParameter('vrDisplayNodeID', '')
        pNode.SetParameter('intensityThreshRangeMin', '')
        pNode.SetParameter('intensityThreshRangeMax', '')
        pNode.SetParameter('vrThreshRange', '')

        Helper.SetLabelVolume('')

        self.EditorWidget.exit()

        if self.__RestartActivated:
            self.workflow().goForward()

    def onThresholdChanged(self):

        if self.__vrOpacityMap == None:
            return

        range0 = self.__threshRange.minimumValue
        range1 = self.__threshRange.maximumValue

        self.__vrOpacityMap.RemoveAllPoints()
        self.__vrOpacityMap.AddPoint(range0 - 75, 0)
        self.__vrOpacityMap.AddPoint(range0, .02)
        self.__vrOpacityMap.AddPoint(range1, .04)
        self.__vrOpacityMap.AddPoint(range1 + 75, .1)

    def killButton(self):

        stepButtons = slicer.util.findChildren(className='ctkPushButton')

        backButton = ''
        nextButton = ''
        for stepButton in stepButtons:
            if stepButton.text == 'Next':
                nextButton = stepButton
            if stepButton.text == 'Back':
                backButton = stepButton

        nextButton.hide()

        # ctk creates a useless final page button. This method gets rid of it.
        bl = slicer.util.findChildren(text='ReviewStep')
        ex = slicer.util.findChildren('', 'EditColorFrame')
        if len(bl):
            bl[0].hide()
        if len(ex):
            ex[0].hide()

        self.__editLabelMapsFrame = slicer.util.findChildren(
            '', 'EditLabelMapsFrame')[0]
        self.__toolsColor = EditorLib.EditColor(self.__editLabelMapsFrame)

    def validate(self, desiredBranchId):

        # For now, no validation required.
        self.__parent.validationSucceeded(desiredBranchId)

    def onEntry(self, comingFrom, transitionType):
        super(ReviewStep, self).onEntry(comingFrom, transitionType)

        self.__RestartActivated = True
        self.__DefaultToolButton = slicer.util.findChildren(
            name='DefaultToolToolButton')[0]

        pNode = self.parameterNode()

        self.updateWidgetFromParameters(pNode)

        Helper.SetBgFgVolumes(self.__visualizedID, '')
        Helper.SetLabelVolume(self.__thresholdedLabelNode.GetID())

        self.onThresholdChanged()

        pNode.SetParameter('currentStep', self.stepid)

        qt.QTimer.singleShot(0, self.killButton)

    def updateWidgetFromParameters(self, pNode):

        self.__clippingModelNode = Helper.getNodeByID(
            pNode.GetParameter('clippingModelNodeID'))
        self.__baselineVolumeID = pNode.GetParameter('baselineVolumeID')
        self.__followupVolumeID = pNode.GetParameter('followupVolumeID')
        self.__subtractVolumeID = pNode.GetParameter('subtractVolumeID')
        self.__croppedVolumeID = pNode.GetParameter('cropedVolumeID')
        self.__baselineVolumeNode = Helper.getNodeByID(self.__baselineVolumeID)
        self.__followupVolumeNode = Helper.getNodeByID(self.__followupVolumeID)
        self.__subtractVolumeNode = Helper.getNodeByID(self.__subtractVolumeID)
        self.__vrDisplayNodeID = pNode.GetParameter('vrDisplayNodeID')
        self.__thresholdedLabelNode = Helper.getNodeByID(
            pNode.GetParameter('thresholdedLabelID'))

        self.__clippingModelNode.GetDisplayNode().VisibilityOn()

        if self.__followupVolumeID == None or self.__followupVolumeID == '':
            self.__visualizedNode = self.__baselineVolumeNode
            self.__visualizedID = self.__baselineVolumeID
        else:
            self.__visualizedID = self.__followupVolumeID
            self.__visualizedNode = self.__followupVolumeNode

        # vrRange = self.__visualizedNode.GetImageData().GetScalarRange()

        # if self.__vrDisplayNode == None:
        #   if self.__vrDisplayNodeID != '':
        #       self.__vrDisplayNode = slicer.mrmlScene.GetNodeByID(self.__vrDisplayNodeID)

        # Replace this, most likely.
        # self.__visualizedNode.AddAndObserveDisplayNodeID(self.__vrDisplayNode.GetID())
        # Helper.InitVRDisplayNode(self.__vrDisplayNode, self.__visualizedID, self.__croppedVolumeID)

        # self.__threshRange.minimum = vrRange[0]
        # self.__threshRange.maximum = vrRange[1]

        # if pNode.GetParameter('vrThreshRangeMin') == '' or pNode.GetParameter('vrThreshRangeMin') == None:
        #   self.__threshRange.setValues(vrRange[1]/3, 2*vrRange[1]/3)
        # else:
        #   self.__threshRange.setValues(float(pNode.GetParameter('vrThreshRangeMin')), float(pNode.GetParameter('vrThreshRangeMax')))

        # self.__vrOpacityMap = self.__vrDisplayNode.GetVolumePropertyNode().GetVolumeProperty().GetScalarOpacity()
        # self.__vrColorMap = self.__vrDisplayNode.GetVolumePropertyNode().GetVolumeProperty().GetRGBTransferFunction()

        # self.__vrColorMap.RemoveAllPoints()
        # self.__vrColorMap.AddRGBPoint(vrRange[0], 0.8, 0.8, 0)
        # self.__vrColorMap.AddRGBPoint(vrRange[1], 0.8, 0.8, 0)

        self.hideUnwantedEditorUIElements()

    def onExit(self, goingTo, transitionType):

        self.__DefaultToolButton.click()

        super(GBMWizardStep, self).onExit(goingTo, transitionType)
Пример #6
0
class ReviewStep(BeersSingleStep):
    def __init__(self, stepid):
        """ This method creates a drop-down menu that includes the whole step.
			The description also acts as a tooltip for the button. There may be 
			some way to override this. The initialize method is inherited
			from ctk.
		"""
        self.initialize(stepid)
        self.setName('6. Review')
        self.setDescription(
            'The segment from the subtraction map is now overlaid on your post-contrast image. Use the threshold bar below to edit the volume rendering node. Use the official Volume Rendering module for more specific visualization.'
        )

        self.__pNode = None
        self.__vrDisplayNode = None
        self.__threshold = [-1, -1]

        # initialize VR stuff
        self.__vrLogic = slicer.modules.volumerendering.logic()
        self.__vrOpacityMap = None

        self.__roiSegmentationNode = None
        self.__roiVolume = None

        self.__parent = super(ReviewStep, self)
        self.__RestartActivated = False

    def createUserInterface(self):
        """ This step is mostly empty. A volume rendering threshold is added to be useful.
		"""

        self.__layout = self.__parent.createUserInterface()

        self.__threshRange = slicer.qMRMLRangeWidget()
        self.__threshRange.decimals = 0
        self.__threshRange.singleStep = 1
        self.__threshRange.connect('valuesChanged(double,double)',
                                   self.onThresholdChanged)
        qt.QTimer.singleShot(0, self.killButton)

        ThreshGroupBox = qt.QGroupBox()
        ThreshGroupBox.setTitle('3D Visualization Intensity Threshold')
        ThreshGroupBoxLayout = qt.QFormLayout(ThreshGroupBox)
        ThreshGroupBoxLayout.addRow(self.__threshRange)
        self.__layout.addRow(ThreshGroupBox)

        editorWidgetParent = slicer.qMRMLWidget()
        editorWidgetParent.setLayout(qt.QVBoxLayout())
        editorWidgetParent.setMRMLScene(slicer.mrmlScene)
        self.__editorWidget = EditorWidget(parent=editorWidgetParent)
        self.__editorWidget.setup()
        self.__layout.addRow(editorWidgetParent)
        self.hideUnwantedEditorUIElements()

        RestartGroupBox = qt.QGroupBox()
        RestartGroupBox.setTitle('Restart')
        RestartGroupBoxLayout = qt.QFormLayout(RestartGroupBox)

        self.__RestartButton = qt.QPushButton('Return to Step 1')
        RestartGroupBoxLayout.addRow(self.__RestartButton)

        self.__RemoveCroppedSubtractionMap = qt.QCheckBox()
        self.__RemoveCroppedSubtractionMap.checked = True
        self.__RemoveCroppedSubtractionMap.setToolTip(
            "Delete the cropped version of your subtaction map.")
        RestartGroupBoxLayout.addRow("Delete cropped subtraction map: ",
                                     self.__RemoveCroppedSubtractionMap)

        self.__RemoveFullSubtracitonMap = qt.QCheckBox()
        self.__RemoveFullSubtracitonMap.checked = True
        self.__RemoveFullSubtracitonMap.setToolTip(
            "Delete the full version of your subtaction map.")
        RestartGroupBoxLayout.addRow("Delete full subtraction map: ",
                                     self.__RemoveFullSubtracitonMap)

        self.__RemoveROI = qt.QCheckBox()
        self.__RemoveROI.checked = False
        self.__RemoveROI.setToolTip(
            "Delete the ROI resulting from your subtaction map.")
        RestartGroupBoxLayout.addRow("Delete ROI: ", self.__RemoveROI)

        # self.__RestartButton.setEnabled(0)

        self.__RestartButton.connect('clicked()', self.Restart)
        self.__RestartActivated = True

        self.__layout.addRow(RestartGroupBox)

    def hideUnwantedEditorUIElements(self):
        print self.__editorWidget.editBoxFrame
        self.__editorWidget.volumes.hide()
        print dir(self.__editorWidget)
        print slicer.util.findChildren()
        # for widgetName in slicer.util.findChildren(self.__editorWidget.editBoxFrame):
        for widgetName in slicer.util.findChildren(self.__editorWidget.helper):
            # widget = slicer.util.findChildren(self.__editorWidget.editBoxFrame)
            print widgetName.objectName
            # print widgetName.parent.name
            # widgetName.hide()
        for widget in [
                'DrawEffectToolButton', 'RectangleEffectToolButton',
                'IdentifyIslandsEffectToolButton',
                'RemoveIslandsEffectToolButton', 'SaveIslandEffectToolButton',
                'RowFrame2'
        ]:
            slicer.util.findChildren(self.__editorWidget.editBoxFrame,
                                     widget)[0].hide()
        print slicer.util.findChildren('', 'EditColorFrame')

    def Restart(self):
        print self.__pNode

        slicer.mrmlScene.RemoveNode(testVolume)

        self.__pNode.SetParameter('baselineVolumeID', None)
        self.__pNode.SetParameter('croppedSubtractVolumeID', None)
        self.__pNode.SetParameter('croppedSubtractVolumeSegmentationID', None)
        self.__pNode.SetParameter('followupVolumeID', None)
        self.__pNode.SetParameter('roiNodeID', None)
        self.__pNode.SetParameter('roiTransformID', None)
        self.__pNode.SetParameter('subtractVolumeID', None)
        self.__pNode.SetParameter('vrDisplayNodeID', None)
        self.__pNode.SetParameter('thresholdRange', None)
        if self.__RestartActivated:
            self.workflow().goForward()

    def onThresholdChanged(self):

        if self.__vrOpacityMap == None:
            return

        range0 = self.__threshRange.minimumValue
        range1 = self.__threshRange.maximumValue

        self.__vrOpacityMap.RemoveAllPoints()
        self.__vrOpacityMap.AddPoint(range0 - 75, 0)
        self.__vrOpacityMap.AddPoint(range0, 1)
        self.__vrOpacityMap.AddPoint(range1, 1)
        self.__vrOpacityMap.AddPoint(range1 + 75, 0)

    def killButton(self):

        # ctk creates a useless final page button. This method gets rid of it.
        bl = slicer.util.findChildren(text='ReviewStep')
        ex = slicer.util.findChildren('', 'EditColorFrame')
        if len(bl):
            bl[0].hide()
        if len(ex):
            ex[0].hide()
            print 'success'
        else:
            print 'fail'

        self.__editLabelMapsFrame = slicer.util.findChildren(
            '', 'EditLabelMapsFrame')[0]
        self.__toolsColor = EditorLib.EditColor(self.__editLabelMapsFrame)

    def validate(self, desiredBranchId):

        # For now, no validation required.
        self.__parent.validationSucceeded(desiredBranchId)

    def onEntry(self, comingFrom, transitionType):
        super(ReviewStep, self).onEntry(comingFrom, transitionType)

        # self.__RestartButton.setEnabled(1)
        self.__RestartActivated = True

        pNode = self.parameterNode()
        self.__pNode = pNode

        self.__clippingModelNode = Helper.getNodeByID(
            pNode.GetParameter('clippingModelNodeID'))
        print self.__clippingModelNode
        print pNode.GetParameter('clippingModelNodeID')
        self.__baselineVolumeID = pNode.GetParameter('baselineVolumeID')
        self.__followupVolumeID = pNode.GetParameter('followupVolumeID')
        self.__subtractVolumeID = pNode.GetParameter('subtractVolumeID')
        self.__roiNodeID = pNode.GetParameter('roiNodeID')
        self.__followupVolumeNode = Helper.getNodeByID(self.__followupVolumeID)
        self.__subtractVolumeNode = Helper.getNodeByID(self.__subtractVolumeID)
        self.__vrDisplayNodeID = pNode.GetParameter('vrDisplayNodeID')
        self.__roiSegmentationNode = Helper.getNodeByID(
            pNode.GetParameter('croppedSubtractVolumeSegmentationID'))
        self.__roiVolumeNode = Helper.getNodeByID(
            pNode.GetParameter('croppedSubtractVolumeID'))

        self.__editorWidget.setMergeNode(self.__roiSegmentationNode)
        self.__clippingModelNode.GetDisplayNode().VisibilityOn()

        followupRange = self.__followupVolumeNode.GetImageData(
        ).GetScalarRange()
        ROIRange = self.__roiSegmentationNode.GetImageData().GetScalarRange()

        if self.__vrDisplayNode == None:
            if self.__vrDisplayNodeID != '':
                self.__vrDisplayNode = slicer.mrmlScene.GetNodeByID(
                    self.__vrDisplayNodeID)

        self.__vrDisplayNode.SetCroppingEnabled(1)
        self.__followupVolumeNode.AddAndObserveDisplayNodeID(
            self.__vrDisplayNode.GetID())
        Helper.InitVRDisplayNode(self.__vrDisplayNode, self.__followupVolumeID,
                                 self.__roiNodeID)

        self.__threshRange.minimum = followupRange[0]
        self.__threshRange.maximum = followupRange[1]
        self.__threshRange.setValues(followupRange[1] / 3,
                                     2 * followupRange[1] / 3)

        self.__vrOpacityMap = self.__vrDisplayNode.GetVolumePropertyNode(
        ).GetVolumeProperty().GetScalarOpacity()
        vrColorMap = self.__vrDisplayNode.GetVolumePropertyNode(
        ).GetVolumeProperty().GetRGBTransferFunction()

        vrColorMap.RemoveAllPoints()
        vrColorMap.AddRGBPoint(followupRange[0], 0.8, 0.8, 0)
        vrColorMap.AddRGBPoint(followupRange[1], 0.8, 0.8, 0)

        self.__vrDisplayNode.VisibilityOn()

        # threshRange = [self.__threshRange.minimumValue, self.__threshRange.maximumValue]
        # self.__vrOpacityMap.RemoveAllPoints()
        # self.__vrOpacityMap.AddPoint(threshRange[1]/2-75,0)
        # self.__vrOpacityMap.AddPoint(threshRange[1]/2,1)
        # self.__vrOpacityMap.AddPoint(threshRange[1]/2+200,1)
        # self.__vrOpacityMap.AddPoint(threshRange[1]/2+250,0)

        Helper.SetBgFgVolumes(self.__baselineVolumeID, self.__followupVolumeID)
        Helper.SetLabelVolume(self.__roiSegmentationNode.GetID())

        self.onThresholdChanged()

        pNode.SetParameter('currentStep', self.stepid)

        qt.QTimer.singleShot(0, self.killButton)

    def onExit(self, goingTo, transitionType):
        # extra error checking, in case the user manages to click ReportROI button
        super(BeersSingleStep, self).onExit(goingTo, transitionType)