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)
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()
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)
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)
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)