def paintPixel(self, x, y): """ paint with a single pixel (in label space) """ sliceLogic = self.sliceWidget.sliceLogic() labelLogic = sliceLogic.GetLabelLayer() labelNode = labelLogic.GetVolumeNode() labelImage = labelNode.GetImageData() if not labelNode: # if there's no label, we can't paint return xyToIJK = labelLogic.GetXYToIJKTransform() ijkFloat = xyToIJK.TransformDoublePoint((x, y, 0)) ijk = [] for e in ijkFloat: try: index = int(round(e)) except ValueError: return ijk.append(index) dims = labelImage.GetDimensions() for e, d in zip(ijk, dims): # clamp to volume extent if e < 0 or e >= d: return parameterNode = EditUtil.getParameterNode() paintLabel = int(parameterNode.GetParameter("label")) labelImage.SetScalarComponentFromFloat(ijk[0], ijk[1], ijk[2], 0, paintLabel) EditUtil.markVolumeNodeAsModified(labelNode)
def deleteSelectedStructure(self, confirm=True): """delete the currently selected structure""" merge = self.merge if not merge: return selectionModel = self.structuresView.selectionModel() selected = selectionModel.currentIndex().row() if selected >= 0: structureName = self.structures.item(selected,2).text() labelNode = slicer.util.getNode(self.structures.item(selected,3).text()) if confirm: if not slicer.util.confirmOkCancelDisplay("Delete \'%s\' volume?" % structureName, windowTitle='Editor'): return slicer.mrmlScene.SaveStateForUndo() slicer.mrmlScene.RemoveNode( labelNode ) self.updateStructures() if self.structures.rowCount() > 0: self.selectStructure((selected-1) if (selected-1 >= 0) else 0) else: EditUtil.setActiveVolumes(self.master, merge) self.edit(0)
def changeLabel(self): # # change the label values based on the parameter node # if not self.sliceLogic: self.sliceLogic = EditUtil.getSliceLogic() parameterNode = EditUtil.getParameterNode() parameterNode = EditUtil.getParameterNode() inputColor = int(parameterNode.GetParameter("ChangeLabelEffect,inputColor")) outputColor = int(parameterNode.GetParameter("ChangeLabelEffect,outputColor")) change = slicer.vtkImageLabelChange() if vtk.VTK_MAJOR_VERSION <= 5: change.SetInput( self.getScopedLabelInput() ) else: change.SetInputData( self.getScopedLabelInput() ) change.SetOutput( self.getScopedLabelOutput() ) change.SetInputLabel( inputColor ) change.SetOutputLabel( outputColor ) # TODO #$this setProgressFilter $change "Change Label" change.Update() self.applyScopedLabel() change.SetOutput( None )
def changeLabel(self): # # change the label values based on the parameter node # if not self.sliceLogic: self.sliceLogic = EditUtil.getSliceLogic() parameterNode = EditUtil.getParameterNode() parameterNode = EditUtil.getParameterNode() inputColor = int( parameterNode.GetParameter("ChangeLabelEffect,inputColor")) outputColor = int( parameterNode.GetParameter("ChangeLabelEffect,outputColor")) change = slicer.vtkImageLabelChange() if vtk.VTK_MAJOR_VERSION <= 5: change.SetInput(self.getScopedLabelInput()) else: change.SetInputData(self.getScopedLabelInput()) change.SetOutput(self.getScopedLabelOutput()) change.SetInputLabel(inputColor) change.SetOutputLabel(outputColor) # TODO #$this setProgressFilter $change "Change Label" change.Update() self.applyScopedLabel() change.SetOutput(None)
def export(self): """create a DICOM Segmentation Object from the current labels and put it in the slicer dicom database""" if not hasattr(slicer.modules, "encodeseg"): # TODO: change this message when EncodeSEG is in the trunk qt.QMessageBox.critical( slicer.util.mainWindow(), "DICOM", "The Reporting extension must be installed in order to export segmentation objects", ) return if not self.master.GetAttribute("DICOM.instanceUIDs"): qt.QMessageBox.critical(slicer.util.mainWindow(), "DICOM", "Master volume must have DICOM context") return rows = self.structures.rowCount() if rows == 0: self.split() rows = self.structures.rowCount() if rows == 0: logging.info("Cannot export empty segmentation") # TODO: should you be able to? return EditUtil.exportAsDICOMSEG(self.master) logging.info("Segmentations exported to DICOM Database")
def export(self): """create a DICOM Segmentation Object from the current labels and put it in the slicer dicom database""" if not hasattr(slicer.modules, 'encodeseg'): # TODO: change this message when EncodeSEG is in the trunk qt.QMessageBox.critical( slicer.util.mainWindow(), "DICOM", "The Reporting extension must be installed in order to export segmentation objects" ) return if not self.master.GetAttribute("DICOM.instanceUIDs"): qt.QMessageBox.critical(slicer.util.mainWindow(), "DICOM", "Master volume must have DICOM context") return rows = self.structures.rowCount() if rows == 0: self.split() rows = self.structures.rowCount() if rows == 0: logging.info("Cannot export empty segmentation" ) # TODO: should you be able to? return EditUtil.exportAsDICOMSEG(self.master) logging.info("Segmentations exported to DICOM Database")
def deleteStructures(self, confirm=True): """delete all the structures""" # # iterate through structures and delete them # merge = self.merge if not merge: return rows = self.structures.rowCount() if confirm: if not slicer.util.confirmOkCancelDisplay( "Delete %d structure volume(s)?" % rows, windowTitle='Editor'): return slicer.mrmlScene.SaveStateForUndo() volumeNodes = self.structureVolumes() for volumeNode in volumeNodes: slicer.mrmlScene.RemoveNode(volumeNode) self.updateStructures() EditUtil.setActiveVolumes(self.master, merge) self.edit(0)
def deleteSelectedStructure(self, confirm=True): """delete the currently selected structure""" merge = self.merge if not merge: return selectionModel = self.structuresView.selectionModel() selected = selectionModel.currentIndex().row() if selected >= 0: structureName = self.structures.item(selected, 2).text() labelNode = slicer.util.getNode( self.structures.item(selected, 3).text()) if confirm: if not slicer.util.confirmOkCancelDisplay( "Delete \'%s\' volume?" % structureName, windowTitle='Editor'): return slicer.mrmlScene.SaveStateForUndo() slicer.mrmlScene.RemoveNode(labelNode) self.updateStructures() if self.structures.rowCount() > 0: self.selectStructure((selected - 1) if (selected - 1 >= 0) else 0) else: EditUtil.setActiveVolumes(self.master, merge) self.edit(0)
def paintPixel(self, x, y): """ paint with a single pixel (in label space) """ sliceLogic = self.sliceWidget.sliceLogic() labelLogic = sliceLogic.GetLabelLayer() labelNode = labelLogic.GetVolumeNode() labelImage = labelNode.GetImageData() if not labelNode: # if there's no label, we can't paint return xyToIJK = labelLogic.GetXYToIJKTransform() ijkFloat = xyToIJK.TransformDoublePoint( (x, y, 0) ) ijk = [] for e in ijkFloat: try: index = int(round(e)) except ValueError: return ijk.append(index) dims = labelImage.GetDimensions() for e,d in zip(ijk,dims): # clamp to volume extent if e < 0 or e >= d: return parameterNode = EditUtil.getParameterNode() paintLabel = int(parameterNode.GetParameter("label")) labelImage.SetScalarComponentFromFloat(ijk[0],ijk[1],ijk[2],0, paintLabel) EditUtil.markVolumeNodeAsModified(labelNode)
def _onParameterNodeModified(self, caller, event=-1): self._onEffectChanged(caller.GetParameter("effect")) EditUtil.setEraseEffectEnabled(EditUtil.isEraseEffectEnabled()) self.actions["EraseLabel"].checked = EditUtil.isEraseEffectEnabled() effectName = EditUtil.getCurrentEffect() if effectName not in self.actions: print('Warning: effect %s not a valid action' % effectName) return self.actions[effectName].checked = True
def split(self): """split the merge volume into individual structures""" self.statusText("Splitting...") if self.merge: EditUtil.splitPerStructureVolumes(self.master, self.merge) self.updateStructures() else: logging.info("No merged label map, cannot split") self.statusText("Finished splitting.")
def split(self): """split the merge volume into individual structures""" self.statusText( "Splitting..." ) if self.merge: EditUtil.splitPerStructureVolumes(self.master, self.merge) self.updateStructures() else: logging.info("No merged label map, cannot split") self.statusText( "Finished splitting." )
def edit(self, label): """select the picked label for editing""" merge = self.merge if not merge: return colorNode = merge.GetDisplayNode().GetColorNode() structureName = colorNode.GetColorName(label) structureVolume = self.structureVolume(structureName) EditUtil.setActiveVolumes(self.master, structureVolume) EditUtil.setLabel(label)
def edit(self,label): """select the picked label for editing""" merge = self.merge if not merge: return colorNode = merge.GetDisplayNode().GetColorNode() structureName = colorNode.GetColorName( label ) structureVolume = self.structureVolume( structureName ) EditUtil.setActiveVolumes(self.master, structureVolume) EditUtil.setLabel(label)
def split(self): """split the merge volume into individual structures""" self.statusText("Splitting...") merge = self.merge if not merge: return colorNode = merge.GetDisplayNode().GetColorNode() accum = vtk.vtkImageAccumulate() if vtk.VTK_MAJOR_VERSION <= 5: accum.SetInput(merge.GetImageData()) else: accum.SetInputConnection(merge.GetImageDataConnection()) accum.Update() lo = int(accum.GetMin()[0]) hi = int(accum.GetMax()[0]) # TODO: pending resolution of bug 1822, run the thresholding # in single threaded mode to avoid data corruption observed on mac release # builds thresholder = vtk.vtkImageThreshold() thresholder.SetNumberOfThreads(1) for i in xrange(lo, hi + 1): self.statusText("Splitting label %d..." % i) if vtk.VTK_MAJOR_VERSION <= 5: thresholder.SetInput(merge.GetImageData()) else: thresholder.SetInputConnection(merge.GetImageDataConnection()) thresholder.SetInValue(i) thresholder.SetOutValue(0) thresholder.ReplaceInOn() thresholder.ReplaceOutOn() thresholder.ThresholdBetween(i, i) thresholder.SetOutputScalarType( merge.GetImageData().GetScalarType()) thresholder.Update() if thresholder.GetOutput().GetScalarRange() != (0.0, 0.0): labelName = colorNode.GetColorName(i) self.statusText("Creating structure volume %s..." % labelName) structureVolume = self.structureVolume(labelName) if not structureVolume: self.addStructure(i, "noEdit") structureVolume = self.structureVolume(labelName) structureVolume.GetImageData().DeepCopy( thresholder.GetOutput()) EditUtil.markVolumeNodeAsModified(structureVolume) self.statusText("Finished splitting.")
def updateParameterNode(self, caller, event): node = EditUtil.getParameterNode() if node != self.parameterNode: if self.parameterNode: node.RemoveObserver(self.parameterNodeTag) self.parameterNode = node self.parameterNodeTag = node.AddObserver(vtk.vtkCommand.ModifiedEvent, self.updateGUIFromMRML)
def __init__(self, sliceWidget): super(PaintEffectTool,self).__init__(sliceWidget) # create a logic instance to do the non-gui work self.logic = PaintEffectLogic(self.sliceWidget.sliceLogic()) # configuration variables self.delayedPaint = True self.parameterNode = EditUtil.getParameterNode() self.sphere = not (0 == int(self.parameterNode.GetParameter("PaintEffect,sphere"))) self.smudge = not (0 == int(self.parameterNode.GetParameter("PaintEffect,smudge"))) self.pixelMode = not (0 == int(self.parameterNode.GetParameter("PaintEffect,pixelMode"))) self.radius = float(self.parameterNode.GetParameter("PaintEffect,radius")) # interaction state variables self.position = [0, 0, 0] self.paintCoordinates = [] self.feedbackActors = [] self.lastRadius = 0 # scratch variables self.rasToXY = vtk.vtkMatrix4x4() # initialization self.brush = vtk.vtkPolyData() self.createGlyph(self.brush) self.mapper = vtk.vtkPolyDataMapper2D() self.actor = vtk.vtkActor2D() self.mapper.SetInputData(self.brush) self.actor.SetMapper(self.mapper) self.actor.VisibilityOff() self.renderer.AddActor2D(self.actor) self.actors.append(self.actor) self.processEvent()
def onApply(self): logic = WatershedFromMarkerEffectLogic( EditUtil.getSliceLogic() ) logic.undoRedo = self.undoRedo logic.sigma = float( self.sigmaSpinBox.value ) logic.doit()
def create(self): self.findEffects() self.mainFrame = qt.QFrame(self.parent) self.mainFrame.objectName = 'MainFrame' vbox = qt.QVBoxLayout() self.mainFrame.setLayout(vbox) self.parent.layout().addWidget(self.mainFrame) # # the buttons # self.rowFrames = [] self.actions = {} self.buttons = {} self.icons = {} self.callbacks = {} # create all of the buttons # createButtonRow() ensures that only effects in self.effects are exposed, self.createButtonRow( ("DefaultTool", "EraseLabel", "PaintEffect", "DrawEffect", "WandEffect", "LevelTracingEffect", "RectangleEffect", "IdentifyIslandsEffect", "ChangeIslandEffect", "RemoveIslandsEffect", "SaveIslandEffect")) self.createButtonRow( ("ErodeEffect", "DilateEffect", "GrowCutEffect", "WatershedFromMarkerEffect", "ThresholdEffect", "ChangeLabelEffect", "MakeModelEffect", "FastMarchingEffect")) extensions = [] for k in slicer.modules.editorExtensions: extensions.append(k) self.createButtonRow(extensions) self.createButtonRow(("PreviousCheckPoint", "NextCheckPoint"), rowLabel="Undo/Redo: ") # # the labels # self.toolsActiveToolFrame = qt.QFrame(self.parent) self.toolsActiveToolFrame.setLayout(qt.QHBoxLayout()) self.parent.layout().addWidget(self.toolsActiveToolFrame) self.toolsActiveTool = qt.QLabel(self.toolsActiveToolFrame) self.toolsActiveTool.setText('Active Tool:') self.toolsActiveTool.setStyleSheet( "background-color: rgb(232,230,235)") self.toolsActiveToolFrame.layout().addWidget(self.toolsActiveTool) self.toolsActiveToolName = qt.QLabel(self.toolsActiveToolFrame) self.toolsActiveToolName.setText('') self.toolsActiveToolName.setStyleSheet( "background-color: rgb(232,230,235)") self.toolsActiveToolFrame.layout().addWidget(self.toolsActiveToolName) vbox.addStretch(1) self.updateUndoRedoButtons() self._onParameterNodeModified(EditUtil.getParameterNode())
def __init__(self, parent=None): VTKObservationMixin.__init__(self) self.editUtil = EditUtil() # Kept for backward compatibility # mrml volume node instances self.master = None self.masterWhenMergeWasSet = None # Editor color LUT self.colorNodeID = None # string self.createMergeOptions = "" self.mergeVolumePostfix = "-label" # slicer helper class self.volumesLogic = slicer.modules.volumes.logic() # widgets that are dynamically created on demand # pseudo signals # - python callable that gets True or False self.selectCommand = None if not parent: self.parent = slicer.qMRMLWidget() self.parent.setLayout(qt.QVBoxLayout()) self.parent.setMRMLScene(slicer.mrmlScene) self.create() self.parent.show() else: self.parent = parent self.create()
def updateGUIFromMRML(self,caller,event): if self.parameterNode.GetParameter(self.parameter) == '': # parameter does not exist - probably initializing return label = int(self.parameterNode.GetParameter(self.parameter)) self.colorNode = EditUtil.getColorNode() if self.colorNode: self.frame.setDisabled(0) self.labelName.setText( self.colorNode.GetColorName( label ) ) lut = self.colorNode.GetLookupTable() rgb = lut.GetTableValue( label ) self.colorPatch.setStyleSheet( "background-color: rgb(%s,%s,%s)" % (rgb[0]*255, rgb[1]*255, rgb[2]*255) ) self.colorSpin.setMaximum( self.colorNode.GetNumberOfColors()-1 ) else: self.frame.setDisabled(1) try: self.colorSpin.setValue(label) except ValueError: # TODO: why does the python class still exist if the widget is destroyed? # - this only happens when reloading the module. The owner of the # instance is gone and the widgets are gone, but this instance still # has observer on the parameter node - this indicates memory leaks # that need to be fixed self.cleanup() return
def showColorBox(self): self.colorNode = EditUtil.getColorNode() if not self.colorBox: self.colorBox = ColorBox.ColorBox(parameterNode=self.parameterNode, parameter=self.parameter, colorNode=self.colorNode) self.colorBox.show(parameterNode=self.parameterNode, parameter=self.parameter, colorNode=self.colorNode)
def updateGUIFromMRML(self, caller, event): if self.parameterNode.GetParameter(self.parameter) == '': # parameter does not exist - probably intializing return label = int(self.parameterNode.GetParameter(self.parameter)) self.colorNode = EditUtil.getColorNode() if self.colorNode: self.frame.setDisabled(0) self.labelName.setText(self.colorNode.GetColorName(label)) lut = self.colorNode.GetLookupTable() rgb = lut.GetTableValue(label) self.colorPatch.setStyleSheet( "background-color: rgb(%s,%s,%s)" % (rgb[0] * 255, rgb[1] * 255, rgb[2] * 255)) self.colorSpin.setMaximum(self.colorNode.GetNumberOfColors() - 1) else: self.frame.setDisabled(1) try: self.colorSpin.setValue(label) except ValueError: # TODO: why does the python class still exist if the widget is destroyed? # - this only happens when reloading the module. The owner of the # instance is gone and the widgets are gone, but this instance still # has observer on the parameter node - this indicates memory leaks # that need to be fixed self.cleanup() return
def onApply(self): logic = WatershedFromMarkerEffectLogic(EditUtil.getSliceLogic()) logic.undoRedo = self.undoRedo logic.sigma = float(self.sigmaSpinBox.value) logic.doit()
def create(self): self.frame = qt.QFrame(self.parent) self.frame.objectName = 'EditColorFrame' self.frame.setLayout(qt.QHBoxLayout()) self.parent.layout().addWidget(self.frame) self.label = qt.QLabel(self.frame) self.label.setText("Label: ") self.frame.layout().addWidget(self.label) self.labelName = qt.QLabel(self.frame) self.labelName.setText("") self.frame.layout().addWidget(self.labelName) self.colorSpin = qt.QSpinBox(self.frame) self.colorSpin.objectName = 'ColorSpinBox' self.colorSpin.setMaximum( 64000) self.colorSpin.setValue( EditUtil.getLabel() ) self.colorSpin.setToolTip( "Click colored patch at right to bring up color selection pop up window. Use the 'c' key to bring up color popup menu." ) self.frame.layout().addWidget(self.colorSpin) self.colorPatch = qt.QPushButton(self.frame) self.colorPatch.setObjectName('ColorPatchButton') self.frame.layout().addWidget(self.colorPatch) self.updateParameterNode(slicer.mrmlScene, vtk.vtkCommand.ModifiedEvent) self.updateGUIFromMRML(self.parameterNode, vtk.vtkCommand.ModifiedEvent) self.frame.connect( 'destroyed()', self.cleanup) self.colorSpin.connect( 'valueChanged(int)', self.updateMRMLFromGUI) self.colorPatch.connect( 'clicked()', self.showColorBox ) # TODO: change this to look for specfic events (added, removed...) # but this requires being able to access events by number from wrapped code self.addObserver(slicer.mrmlScene, vtk.vtkCommand.ModifiedEvent, self.updateParameterNode)
def __init__(self, parent=None): VTKObservationMixin.__init__(self) self.parent = parent self.updatingGUI = False self.widgets = [] self.parameterNode = None self.parameterNodeTag = None self.editUtil = EditUtil() # Kept for backward compatibility self.tools = [] # connections is a list of widget/signal/slot tripples # for the options gui that can be connected/disconnected # as needed to prevent triggering mrml updates while # updating the state of the gui # - each level of the inheritance tree can add entries # to this list for use by the connectWidgets # and disconnectWidgets methods self.connections = [] self.connectionsConnected = False # 1) find the parameter node in the scene and observe it # 2) set the defaults (will only set them if they are not # already set) self.updateParameterNode(self.parameterNode, vtk.vtkCommand.ModifiedEvent) # TODO: change this to look for specfic events (added, removed...) # but this requires being able to access events by number from wrapped code self.addObserver(slicer.mrmlScene, vtk.vtkCommand.ModifiedEvent, self.updateParameterNode)
def split(self): """split the merge volume into individual structures""" self.statusText( "Splitting..." ) merge = self.merge if not merge: return colorNode = merge.GetDisplayNode().GetColorNode() accum = vtk.vtkImageAccumulate() if vtk.VTK_MAJOR_VERSION <= 5: accum.SetInput(merge.GetImageData()) else: accum.SetInputConnection(merge.GetImageDataConnection()) accum.Update() lo = int(accum.GetMin()[0]) hi = int(accum.GetMax()[0]) # TODO: pending resolution of bug 1822, run the thresholding # in single threaded mode to avoid data corruption observed on mac release # builds thresholder = vtk.vtkImageThreshold() thresholder.SetNumberOfThreads(1) for i in xrange(lo,hi+1): self.statusText( "Splitting label %d..."%i ) if vtk.VTK_MAJOR_VERSION <= 5: thresholder.SetInput( merge.GetImageData() ) else: thresholder.SetInputConnection( merge.GetImageDataConnection() ) thresholder.SetInValue( i ) thresholder.SetOutValue( 0 ) thresholder.ReplaceInOn() thresholder.ReplaceOutOn() thresholder.ThresholdBetween( i, i ) thresholder.SetOutputScalarType( merge.GetImageData().GetScalarType() ) thresholder.Update() if thresholder.GetOutput().GetScalarRange() != (0.0, 0.0): labelName = colorNode.GetColorName(i) self.statusText( "Creating structure volume %s..."%labelName ) structureVolume = self.structureVolume( labelName ) if not structureVolume: self.addStructure( i, "noEdit" ) structureVolume = self.structureVolume( labelName ) structureVolume.GetImageData().DeepCopy( thresholder.GetOutput() ) EditUtil.markVolumeNodeAsModified(structureVolume) self.statusText( "Finished splitting." )
def processEvent(self, caller=None, event=None): """ handle events from the render window interactor """ if super(PaintEffectTool,self).processEvent(caller,event): return # interactor events if event == "LeftButtonPressEvent": self.actionState = "painting" if not self.pixelMode: self.cursorOff() xy = self.interactor.GetEventPosition() if self.smudge: EditUtil.setLabel(self.getLabelPixel(xy)) self.paintAddPoint(xy[0], xy[1]) self.abortEvent(event) elif event == "LeftButtonReleaseEvent": self.paintApply() self.actionState = None self.cursorOn() elif event == "MouseMoveEvent": self.actor.VisibilityOn() if self.actionState == "painting": xy = self.interactor.GetEventPosition() self.paintAddPoint(xy[0], xy[1]) self.abortEvent(event) elif event == "EnterEvent": self.actor.VisibilityOn() elif event == "LeaveEvent": self.actor.VisibilityOff() elif event == "KeyPressEvent": key = self.interactor.GetKeySym() if key == 'plus' or key == 'equal': self.scaleRadius(1.2) if key == 'minus' or key == 'underscore': self.scaleRadius(0.8) else: pass # events from the slice node if caller and caller.IsA('vtkMRMLSliceNode'): if hasattr(self,'brush'): self.createGlyph(self.brush) self.positionActors()
def processEvent(self, caller=None, event=None): """ handle events from the render window interactor """ if super(PaintEffectTool, self).processEvent(caller, event): return # interactor events if event == "LeftButtonPressEvent": self.actionState = "painting" if not self.pixelMode: self.cursorOff() xy = self.interactor.GetEventPosition() if self.smudge: EditUtil.setLabel(self.getLabelPixel(xy)) self.paintAddPoint(xy[0], xy[1]) self.abortEvent(event) elif event == "LeftButtonReleaseEvent": self.paintApply() self.actionState = None self.cursorOn() elif event == "MouseMoveEvent": self.actor.VisibilityOn() if self.actionState == "painting": xy = self.interactor.GetEventPosition() self.paintAddPoint(xy[0], xy[1]) self.abortEvent(event) elif event == "EnterEvent": self.actor.VisibilityOn() elif event == "LeaveEvent": self.actor.VisibilityOff() elif event == "KeyPressEvent": key = self.interactor.GetKeySym() if key == 'plus' or key == 'equal': self.scaleRadius(1.2) if key == 'minus' or key == 'underscore': self.scaleRadius(0.8) else: pass # events from the slice node if caller and caller.IsA('vtkMRMLSliceNode'): if hasattr(self, 'brush'): self.createGlyph(self.brush) self.positionActors()
def addStructure(self, label=None, options=""): """create the segmentation helper box""" merge = self.merge if not merge: return if not label: # if no label given, prompt the user. The selectCommand of the colorBox will # then re-invoke this method with the label value set and we will continue label = self.promptStructure() return EditUtil.addStructure(self.master, self.merge, label) self.updateStructures() if options.find("noEdit") < 0: self.selectStructure(self.structures.rowCount() - 1)
def create(self): super(MakeModelEffectOptions, self).create() self.goToModelMaker = qt.QPushButton("Go To Model Maker", self.frame) self.goToModelMaker.setToolTip( "The Model Maker interface contains a whole range of options for building sets of models and controlling the parameters." ) self.frame.layout().addWidget(self.goToModelMaker) self.widgets.append(self.goToModelMaker) self.smooth = qt.QCheckBox("Smooth Model", self.frame) self.smooth.checked = True self.smooth.setToolTip( "When smoothed, the model will look better, but some details of the label map will not be visible on the model. When not smoothed you will see individual voxel boundaries in the model. Smoothing here corresponds to Decimation of 0.25 and Smooting iterations of 10." ) self.frame.layout().addWidget(self.smooth) self.widgets.append(self.smooth) # # model name # self.nameFrame = qt.QFrame(self.frame) self.nameFrame.setLayout(qt.QHBoxLayout()) self.frame.layout().addWidget(self.nameFrame) self.widgets.append(self.nameFrame) self.modelNameLabel = qt.QLabel("Model Name: ", self.nameFrame) self.modelNameLabel.setToolTip( "Select the name for the newly created model.") self.nameFrame.layout().addWidget(self.modelNameLabel) self.widgets.append(self.modelNameLabel) self.modelName = qt.QLineEdit(self.nameFrame) self.modelName.setText(self.getUniqueModelName( EditUtil.getLabelName())) self.nameFrame.layout().addWidget(self.modelName) self.widgets.append(self.modelName) self.apply = qt.QPushButton("Apply", self.frame) self.apply.objectName = self.__class__.__name__ + 'Apply' self.apply.setToolTip( "Build a model for the current label value of the label map being edited in the Red slice window. Model will be created in the background." ) self.frame.layout().addWidget(self.apply) self.widgets.append(self.apply) HelpButton( self.frame, "Use this tool build a model. A subset of model building options is provided here. Go to the Model Maker module to expose a range of parameters. Use Merge and Build button in the Advanced... tab to quickly make a model of all defined structures in the merge label map." ) # Add vertical spacer self.frame.layout().addStretch(1) self.connections.append((self.apply, 'clicked()', self.onApply)) self.connections.append( (self.goToModelMaker, 'clicked()', self.onGoToModelMaker))
def addStructure(self,label=None, options=""): """create the segmentation helper box""" merge = self.merge if not merge: return if not label: # if no label given, prompt the user. The selectCommand of the colorBox will # then re-invoke this method with the label value set and we will continue label = self.promptStructure() return EditUtil.addStructure(self.master, self.merge, label) self.updateStructures() if options.find("noEdit") < 0: self.selectStructure(self.structures.rowCount()-1)
def select(self, masterVolume=None, mergeVolume=None): """select master volume - load merge volume if one with the correct name exists""" if masterVolume == None: masterVolume = self.masterSelector.currentNode() self.master = masterVolume self.masterSelector.blockSignals(True) self.masterSelector.setCurrentNode(self.master) self.masterSelector.blockSignals(False) self.mergeSelector.setCurrentNode(mergeVolume) if self.master and not self.mergeVolume(): # the master exists, but there is no merge volume yet # bring up dialog to create a merge with a user-selected color node self.labelCreateDialog() merge = self.mergeVolume() if merge: if not merge.IsA("vtkMRMLLabelMapVolumeNode"): slicer.util.errorDisplay( "Error: selected merge label volume is not a label volume") else: EditUtil.setActiveVolumes(self.master, merge) self.mergeSelector.setCurrentNode(merge) self.structureListWidget.master = self.master self.structureListWidget.merge = merge self.structureListWidget.updateStructures() if self.master and merge: warnings = self.volumesLogic.CheckForLabelVolumeValidity( self.master, merge) if warnings != "": warnings = "Geometry of master and merge volumes do not match.\n\n" + warnings slicer.util.errorDisplay("Warning: %s" % warnings) # trigger a modified event on the parameter node so that other parts of the GUI # (such as the EditColor) will know to update and enable themselves EditUtil.getParameterNode().Modified() if self.selectCommand: self.selectCommand()
def updateParameterNode(self, caller, event): # # observe the scene to know when to get the parameter node # parameterNode = EditUtil.getParameterNode() if parameterNode != self.parameterNode: if self.parameterNode: self.parameterNode.RemoveObserver(self.parameterNodeTag) self.parameterNode = parameterNode self.parameterNodeTag = self.parameterNode.AddObserver(vtk.vtkCommand.ModifiedEvent, self.updateGUIFromMRML)
def percentMaxChanged(self, val): labelNode = self.logic.getLabelNode() labelImage = EditUtil.getLabelImage() spacing = labelNode.GetSpacing() dim = labelImage.GetDimensions() print dim totalVolume = spacing[0] * dim[0] + spacing[1] * dim[1] + spacing[2] * dim[2] percentVolumeStr = "%.5f" % (totalVolume * val / 100.0) self.percentVolume.text = "(maximum total volume: " + percentVolumeStr + " mL)"
def percentMaxChanged(self, val): labelNode = self.logic.getLabelNode() labelImage = EditUtil.getLabelImage() spacing = labelNode.GetSpacing() dim = labelImage.GetDimensions() print dim totalVolume = spacing[0]*dim[0]+spacing[1]*dim[1]+spacing[2]*dim[2] percentVolumeStr = "%.5f" % (totalVolume*val/100.) self.percentVolume.text = '(maximum total volume: '+percentVolumeStr+' mL)'
def select(self, masterVolume=None, mergeVolume=None): """select master volume - load merge volume if one with the correct name exists""" if masterVolume is None: masterVolume = self.masterSelector.currentNode() self.master = masterVolume self.masterSelector.blockSignals(True) self.masterSelector.setCurrentNode(self.master) self.masterSelector.blockSignals(False) self.mergeSelector.setCurrentNode(mergeVolume) if self.master and not self.mergeVolume(): # the master exists, but there is no merge volume yet # bring up dialog to create a merge with a user-selected color node self.labelCreateDialog() merge = self.mergeVolume() if merge: if not merge.IsA("vtkMRMLLabelMapVolumeNode"): slicer.util.errorDisplay( "Error: selected merge label volume is not a label volume" ) else: EditUtil.setActiveVolumes(self.master, merge) self.mergeSelector.setCurrentNode(merge) self.structureListWidget.master = self.master self.structureListWidget.merge = merge self.structureListWidget.updateStructures() if self.master and merge: warnings = self.volumesLogic.CheckForLabelVolumeValidity(self.master,merge) if warnings != "": warnings = "Geometry of master and merge volumes do not match.\n\n" + warnings slicer.util.errorDisplay( "Warning: %s" % warnings ) # trigger a modified event on the parameter node so that other parts of the GUI # (such as the EditColor) will know to update and enable themselves EditUtil.getParameterNode().Modified() if self.selectCommand: self.selectCommand()
def getBackgroundScalarRange(self): success = False lo = -1 hi = -1 backgroundVolume = EditUtil.getBackgroundVolume() if backgroundVolume: backgroundImage = backgroundVolume.GetImageData() if backgroundImage: lo, hi = backgroundImage.GetScalarRange() success = True return success, lo, hi
def __init__(self, parent=0): super(FastMarchingEffectOptions,self).__init__(parent) # self.attributes should be tuple of options: # 'MouseTool' - grabs the cursor # 'Nonmodal' - can be applied while another is active # 'Disabled' - not available # self.attributes = ('MouseTool') self.displayName = 'FastMarchingEffect Effect' self.logic = FastMarchingEffectLogic(EditUtil.getSliceLogic())
def __init__(self, parent=0): super(FastMarchingEffectOptions, self).__init__(parent) # self.attributes should be tuple of options: # 'MouseTool' - grabs the cursor # 'Nonmodal' - can be applied while another is active # 'Disabled' - not available # self.attributes = ('MouseTool') self.displayName = 'FastMarchingEffect Effect' self.logic = FastMarchingEffectLogic(EditUtil.getSliceLogic())
def updateGUIFromMRML(self,caller,event): if self.parameterNode.GetParameter(self.parameter) == '': # parameter does not exist - probably intializing return label = int(self.parameterNode.GetParameter(self.parameter)) self.colorNode = EditUtil.getColorNode() if self.colorNode: self.frame.setDisabled(0) self.labelName.setText( self.colorNode.GetColorName( label ) ) lut = self.colorNode.GetLookupTable() rgb = lut.GetTableValue( label ) self.colorPatch.setStyleSheet( "background-color: rgb(%s,%s,%s)" % (rgb[0]*255, rgb[1]*255, rgb[2]*255) ) self.colorSpin.setMaximum( self.colorNode.GetNumberOfColors()-1 ) else: self.frame.setDisabled(1) try: self.colorSpin.setValue(label) # check to see if there's an associated terminology with this color node if self.colorNode: terminologyName = self.colorNode.GetAttribute("TerminologyName") if terminologyName: colorLogic = slicer.modules.colors.logic() if colorLogic: # enable the terminology widgets self.hideTerminology(0) region = colorLogic.GetAnatomicRegionCodeMeaning(label, terminologyName) regionModifier = colorLogic.GetAnatomicRegionModifierCodeMeaning(label, terminologyName) category = colorLogic.GetSegmentedPropertyCategoryCodeMeaning(label, terminologyName) categoryType = colorLogic.GetSegmentedPropertyTypeCodeMeaning(label, terminologyName) categoryModifier = colorLogic.GetSegmentedPropertyTypeModifierCodeMeaning(label, terminologyName) self.terminologyRegion.setText(region) self.terminologyRegionModifier.setText(regionModifier) self.terminologyCategory.setText(category) self.terminologyCategoryType.setText(categoryType) self.terminologyCategoryModifier.setText(categoryModifier) # if no region information, hide the region panel if region is "" and regionModifier is "": self.terminologyRegionFrame.setHidden(1) else: self.terminologyRegionFrame.setHidden(0) else: self.hideTerminology(1) except ValueError: # TODO: why does the python class still exist if the widget is destroyed? # - this only happens when reloading the module. The owner of the # instance is gone and the widgets are gone, but this instance still # has observer on the parameter node - this indicates memory leaks # that need to be fixed self.cleanup() return
def paintApply(self): if self.paintCoordinates != []: if self.undoRedo: self.undoRedo.saveState() for xy in self.paintCoordinates: if self.pixelMode: self.paintPixel(xy[0], xy[1]) else: self.paintBrush(xy[0], xy[1]) self.paintCoordinates = [] self.paintFeedback() # TODO: workaround for new pipeline in slicer4 # - editing image data of the calling modified on the node # does not pull the pipeline chain # - so we trick it by changing the image data first sliceLogic = self.sliceWidget.sliceLogic() labelLogic = sliceLogic.GetLabelLayer() labelNode = labelLogic.GetVolumeNode() EditUtil.markVolumeNodeAsModified(labelNode)
def updateParameterNode(self, caller, event): """ note: this method needs to be implemented exactly as defined in the leaf classes in EditOptions.py in each leaf subclass so that "self" in the observer is of the correct type """ node = EditUtil.getParameterNode() if node != self.parameterNode: if self.parameterNode: node.RemoveObserver(self.parameterNodeTag) self.parameterNode = node self.parameterNodeTag = node.AddObserver(vtk.vtkCommand.ModifiedEvent, self.updateGUIFromMRML)
def updateParameterNode(self, caller, event): """ note: this method needs to be implemented exactly as defined in the leaf classes in EditOptions.py in each leaf subclass so that "self" in the observer is of the correct type """ node = EditUtil.getParameterNode() if node != self.parameterNode: if self.parameterNode: node.RemoveObserver(self.parameterNodeTag) self.parameterNode = node self.parameterNodeTag = node.AddObserver( vtk.vtkCommand.ModifiedEvent, self.updateGUIFromMRML)
def create(self): self.findEffects() self.mainFrame = qt.QFrame(self.parent) self.mainFrame.objectName = 'MainFrame' vbox = qt.QVBoxLayout() self.mainFrame.setLayout(vbox) self.parent.layout().addWidget(self.mainFrame) # # the buttons # self.rowFrames = [] self.actions = {} self.buttons = {} self.icons = {} self.callbacks = {} # create all of the buttons # createButtonRow() ensures that only effects in self.effects are exposed, self.createButtonRow( ("DefaultTool", "EraseLabel", "PaintEffect", "DrawEffect", "WandEffect", "LevelTracingEffect", "RectangleEffect", "IdentifyIslandsEffect", "ChangeIslandEffect", "RemoveIslandsEffect", "SaveIslandEffect") ) self.createButtonRow( ("ErodeEffect", "DilateEffect", "GrowCutEffect", "WatershedFromMarkerEffect", "ThresholdEffect", "ChangeLabelEffect", "MakeModelEffect", "FastMarchingEffect") ) extensions = [] for k in slicer.modules.editorExtensions: extensions.append(k) self.createButtonRow( extensions ) self.createButtonRow( ("PreviousCheckPoint", "NextCheckPoint"), rowLabel="Undo/Redo: " ) # # the labels # self.toolsActiveToolFrame = qt.QFrame(self.parent) self.toolsActiveToolFrame.setLayout(qt.QHBoxLayout()) self.parent.layout().addWidget(self.toolsActiveToolFrame) self.toolsActiveTool = qt.QLabel(self.toolsActiveToolFrame) self.toolsActiveTool.setText( 'Active Tool:' ) self.toolsActiveTool.setStyleSheet("background-color: rgb(232,230,235)") self.toolsActiveToolFrame.layout().addWidget(self.toolsActiveTool) self.toolsActiveToolName = qt.QLabel(self.toolsActiveToolFrame) self.toolsActiveToolName.setText( '' ) self.toolsActiveToolName.setStyleSheet("background-color: rgb(232,230,235)") self.toolsActiveToolFrame.layout().addWidget(self.toolsActiveToolName) vbox.addStretch(1) self.updateUndoRedoButtons() self._onParameterNodeModified(EditUtil.getParameterNode())
def percentMaxChanged(self, val): labelNode = self.logic.getLabelNode() labelImage = EditUtil.getLabelImage() spacing = labelNode.GetSpacing() if vtk.VTK_MAJOR_VERSION <= 5: dim = labelImage.GetDimensions() totalVolume = spacing[0]*(dim[1]+1)+spacing[1]*(dim[3]+1)+spacing[2]*(dim[5]+1) else: dim = labelImage.GetDimensions() print dim totalVolume = spacing[0]*dim[0]+spacing[1]*dim[1]+spacing[2]*dim[2] percentVolumeStr = "%.5f" % (totalVolume*val/100.) self.percentVolume.text = '(maximum total volume: '+percentVolumeStr+' mL)'
def deleteStructures(self, confirm=True): """delete all the structures""" # # iterate through structures and delete them # merge = self.merge if not merge: return rows = self.structures.rowCount() if confirm: if not slicer.util.confirmOkCancelDisplay("Delete %d structure volume(s)?" % rows, windowTitle='Editor'): return slicer.mrmlScene.SaveStateForUndo() volumeNodes = self.structureVolumes() for volumeNode in volumeNodes: slicer.mrmlScene.RemoveNode( volumeNode ) self.updateStructures() EditUtil.setActiveVolumes(self.master, merge) self.edit(0)
def updateLabel(self,value): if not self.fm: return self.fm.show(value) self.fm.Modified() self.fm.Update() EditUtil.getLabelImage().DeepCopy(self.fm.GetOutput()) EditUtil.getLabelImage().Modified() EditUtil.markVolumeNodeAsModified(self.sliceLogic.GetLabelLayer().GetVolumeNode())
def create(self): super(MakeModelEffectOptions,self).create() self.goToModelMaker = qt.QPushButton("Go To Model Maker", self.frame) self.goToModelMaker.setToolTip( "The Model Maker interface contains a whole range of options for building sets of models and controlling the parameters." ) self.frame.layout().addWidget(self.goToModelMaker) self.widgets.append(self.goToModelMaker) self.smooth = qt.QCheckBox("Smooth Model", self.frame) self.smooth.checked = True self.smooth.setToolTip("When smoothed, the model will look better, but some details of the label map will not be visible on the model. When not smoothed you will see individual voxel boundaries in the model. Smoothing here corresponds to Decimation of 0.25 and Smooting iterations of 10.") self.frame.layout().addWidget(self.smooth) self.widgets.append(self.smooth) # # model name # self.nameFrame = qt.QFrame(self.frame) self.nameFrame.setLayout(qt.QHBoxLayout()) self.frame.layout().addWidget(self.nameFrame) self.widgets.append(self.nameFrame) self.modelNameLabel = qt.QLabel("Model Name: ", self.nameFrame) self.modelNameLabel.setToolTip( "Select the name for the newly created model." ) self.nameFrame.layout().addWidget(self.modelNameLabel) self.widgets.append(self.modelNameLabel) self.modelName = qt.QLineEdit(self.nameFrame) self.modelName.setText( self.getUniqueModelName( EditUtil.getLabelName() ) ) self.nameFrame.layout().addWidget(self.modelName) self.widgets.append(self.modelName) self.apply = qt.QPushButton("Apply", self.frame) self.apply.objectName = self.__class__.__name__ + 'Apply' self.apply.setToolTip("Build a model for the current label value of the label map being edited in the Red slice window. Model will be created in the background." ) self.frame.layout().addWidget(self.apply) self.widgets.append(self.apply) HelpButton(self.frame, "Use this tool build a model. A subset of model building options is provided here. Go to the Model Maker module to expose a range of parameters. Use Merge and Build button in the Advanced... tab to quickly make a model of all defined structures in the merge label map.") # Add vertical spacer self.frame.layout().addStretch(1) self.connections.append( (self.apply, 'clicked()', self.onApply) ) self.connections.append( (self.goToModelMaker, 'clicked()', self.onGoToModelMaker) )
def __init__(self, sliceWidget): super(PaintEffectTool, self).__init__(sliceWidget) # create a logic instance to do the non-gui work self.logic = PaintEffectLogic(self.sliceWidget.sliceLogic()) # configuration variables self.delayedPaint = True self.parameterNode = EditUtil.getParameterNode() self.sphere = not (0 == int( self.parameterNode.GetParameter("PaintEffect,sphere"))) self.smudge = not (0 == int( self.parameterNode.GetParameter("PaintEffect,smudge"))) self.pixelMode = not (0 == int( self.parameterNode.GetParameter("PaintEffect,pixelMode"))) self.radius = float( self.parameterNode.GetParameter("PaintEffect,radius")) # interaction state variables self.position = [0, 0, 0] self.paintCoordinates = [] self.feedbackActors = [] self.lastRadius = 0 # scratch variables self.rasToXY = vtk.vtkMatrix4x4() # initialization self.brush = vtk.vtkPolyData() self.createGlyph(self.brush) self.mapper = vtk.vtkPolyDataMapper2D() self.actor = vtk.vtkActor2D() if vtk.VTK_MAJOR_VERSION <= 5: self.mapper.SetInput(self.brush) else: self.mapper.SetInputData(self.brush) self.actor.SetMapper(self.mapper) self.actor.VisibilityOff() self.renderer.AddActor2D(self.actor) self.actors.append(self.actor) self.processEvent()
def setQuickieRadius(self,radius): labelVolume = EditUtil.getLabelVolume() if labelVolume: if self.radiusUnitsToggle.text == 'px:': spacing = labelVolume.GetSpacing() if self.radiusPixelMode == 'diag': from math import sqrt diag = sqrt(reduce(lambda x,y:x+y, map(lambda x: x**2, spacing))) mmRadius = diag * radius elif self.radiusPixelMode == 'min': mmRadius = min(spacing) * radius else: print (self,"Unknown pixel mode - using 5mm") mmRadius = 5 else: mmRadius = radius self.disconnectWidgets() self.radiusSpinBox.setValue(mmRadius) self.radius.setValue(mmRadius) self.connectWidgets() self.updateMRMLFromGUI()
def __init__(self, parent=0): # get pixel-size-dependent parameters # calculate this before calling superclass init # so it can be used to set mrml defaults if needed labelVolume = EditUtil.getLabelVolume() if labelVolume and labelVolume.GetImageData(): spacing = labelVolume.GetSpacing() dimensions = labelVolume.GetImageData().GetDimensions() self.minimumRadius = 0.5 * min(spacing) bounds = [a * b for a, b in zip(spacing, dimensions)] self.maximumRadius = 0.5 * max(bounds) else: self.minimumRadius = 0.01 self.maximumRadius = 100 super(PaintEffectOptions, self).__init__(parent) # option to use 'min' or 'diag' # - min means pixel radius is min spacing # - diag means corner to corner length self.radiusPixelMode = 'min'