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 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 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 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, 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 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 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 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 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 __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 paintBrush(self, x, y): """ paint with a brush that is circular (or optionally spherical) in XY space (could be stretched or rotate when transformed to IJK) - make sure to hit every pixel in IJK space - apply the threshold if selected """ sliceLogic = self.sliceWidget.sliceLogic() sliceNode = sliceLogic.GetSliceNode() labelLogic = sliceLogic.GetLabelLayer() labelNode = labelLogic.GetVolumeNode() labelImage = labelNode.GetImageData() backgroundLogic = sliceLogic.GetBackgroundLayer() backgroundNode = backgroundLogic.GetVolumeNode() backgroundImage = backgroundNode.GetImageData() if not labelNode: # if there's no label, we can't paint return # # get the brush bounding box in ijk coordinates # - get the xy bounds # - transform to ijk # - clamp the bounds to the dimensions of the label image # bounds = self.brush.GetPoints().GetBounds() left = x + bounds[0] right = x + bounds[1] bottom = y + bounds[2] top = y + bounds[3] xyToIJK = labelLogic.GetXYToIJKTransform() tlIJK = xyToIJK.TransformDoublePoint((left, top, 0)) trIJK = xyToIJK.TransformDoublePoint((right, top, 0)) blIJK = xyToIJK.TransformDoublePoint((left, bottom, 0)) brIJK = xyToIJK.TransformDoublePoint((right, bottom, 0)) dims = labelImage.GetDimensions() # clamp the top, bottom, left, right to the # valid dimensions of the label image tl = [0, 0, 0] tr = [0, 0, 0] bl = [0, 0, 0] br = [0, 0, 0] for i in xrange(3): tl[i] = int(round(tlIJK[i])) if tl[i] < 0: tl[i] = 0 if tl[i] >= dims[i]: tl[i] = dims[i] - 1 tr[i] = int(round(trIJK[i])) if tr[i] < 0: tr[i] = 0 if tr[i] >= dims[i]: tr[i] = dims[i] - 1 bl[i] = int(round(blIJK[i])) if bl[i] < 0: bl[i] = 0 if bl[i] >= dims[i]: bl[i] = dims[i] - 1 br[i] = int(round(brIJK[i])) if br[i] < 0: br[i] = 0 if br[i] >= dims[i]: br[i] = dims[i] - 1 # If the region is smaller than a pixel then paint it using paintPixel mode, # to make sure at least one pixel is filled on each click maxRowDelta = 0 maxColumnDelta = 0 for i in xrange(3): d = abs(tr[i] - tl[i]) if d > maxColumnDelta: maxColumnDelta = d d = abs(br[i] - bl[i]) if d > maxColumnDelta: maxColumnDelta = d d = abs(bl[i] - tl[i]) if d > maxRowDelta: maxRowDelta = d d = abs(br[i] - tr[i]) if d > maxRowDelta: maxRowDelta = d if maxRowDelta <= 1 or maxColumnDelta <= 1: self.paintPixel(x, y) return # # get the layers and nodes # and ijk to ras matrices including transforms # backgroundIJKToRAS = self.logic.getIJKToRASMatrix(backgroundNode) labelIJKToRAS = self.logic.getIJKToRASMatrix(labelNode) xyToRAS = sliceNode.GetXYToRAS() brushCenter = xyToRAS.MultiplyPoint((x, y, 0, 1))[:3] brushRadius = self.radius bSphere = self.sphere parameterNode = EditUtil.getParameterNode() paintLabel = int(parameterNode.GetParameter("label")) paintOver = int(parameterNode.GetParameter("LabelEffect,paintOver")) paintThreshold = int( parameterNode.GetParameter("LabelEffect,paintThreshold")) paintThresholdMin = float( parameterNode.GetParameter("LabelEffect,paintThresholdMin")) paintThresholdMax = float( parameterNode.GetParameter("LabelEffect,paintThresholdMax")) # # set up the painter class and let 'r rip! # if not hasattr(self, "painter"): self.painter = slicer.vtkImageSlicePaint() self.painter.SetBackgroundImage(backgroundImage) self.painter.SetBackgroundIJKToWorld(backgroundIJKToRAS) self.painter.SetWorkingImage(labelImage) self.painter.SetWorkingIJKToWorld(labelIJKToRAS) self.painter.SetTopLeft(tl[0], tl[1], tl[2]) self.painter.SetTopRight(tr[0], tr[1], tr[2]) self.painter.SetBottomLeft(bl[0], bl[1], bl[2]) self.painter.SetBottomRight(br[0], br[1], br[2]) self.painter.SetBrushCenter(brushCenter[0], brushCenter[1], brushCenter[2]) self.painter.SetBrushRadius(brushRadius) self.painter.SetPaintLabel(paintLabel) self.painter.SetPaintOver(paintOver) self.painter.SetThresholdPaint(paintThreshold) self.painter.SetThresholdPaintRange(paintThresholdMin, paintThresholdMax) if bSphere: # fill volume of a sphere rather than a circle on the currently displayed image slice # Algorithm: ########################### # Assume brushRadius is in mm # Estimate zVoxelSize # Compute number of slices spanned by sphere, that are still within the volume # For each spanned slice # reposition the brushCenter using xy(z)ToRAS transform: i.e. canvas to patient world coordinates # resize the radius: brushRadiusOffset=sqrt(brushRadius*brushRadius - zOffset_mm*zOffset_mm) # invoke Paint() # Finally paint on the center slice, leaving the gui on the center slice being most visibly edited #------------------ # Estimate zVoxelSize_mm brushCenter1 = xyToRAS.MultiplyPoint((x, y, 0, 1))[:3] brushCenter2 = xyToRAS.MultiplyPoint((x, y, 100, 1))[:3] dx1 = brushCenter1[0] - brushCenter2[0] dx2 = brushCenter1[1] - brushCenter2[1] dx3 = brushCenter1[2] - brushCenter2[2] distanceSpannedBy100Slices = sqrt(dx1 * dx1 + dx2 * dx2 + dx3 * dx3) # compute L2 norm if distanceSpannedBy100Slices == 0: zVoxelSize_mm = 1 else: zVoxelSize_mm = distanceSpannedBy100Slices / 100 # -- # Compute number of slices spanned by sphere nNumSlicesInEachDirection = brushRadius / zVoxelSize_mm nNumSlicesInEachDirection = nNumSlicesInEachDirection - 1 sliceOffsetArray = numpy.concatenate((-1 * numpy.arange( 1, nNumSlicesInEachDirection + 1, ), numpy.arange(1, nNumSlicesInEachDirection + 1))) for iSliceOffset in sliceOffsetArray: # x,y uses slice (canvas) coordinate system and actually has a 3rd z component (index into the slice you're looking at) # hence xyToRAS is really performing xyzToRAS. RAS is patient world coordinate system. Note the 1 is because the trasform uses homogeneous coordinates iBrushCenter = xyToRAS.MultiplyPoint( (x, y, iSliceOffset, 1))[:3] self.painter.SetBrushCenter(iBrushCenter[0], iBrushCenter[1], iBrushCenter[2]) # [ ] Need to just continue (pass this loop iteration if the brush center is not within the volume zOffset_mm = zVoxelSize_mm * iSliceOffset brushRadiusOffset = sqrt(brushRadius * brushRadius - zOffset_mm * zOffset_mm) self.painter.SetBrushRadius(brushRadiusOffset) # -- tlIJKtemp = xyToIJK.TransformDoublePoint( (left, top, iSliceOffset)) trIJKtemp = xyToIJK.TransformDoublePoint( (right, top, iSliceOffset)) blIJKtemp = xyToIJK.TransformDoublePoint( (left, bottom, iSliceOffset)) brIJKtemp = xyToIJK.TransformDoublePoint( (right, bottom, iSliceOffset)) # clamp the top, bottom, left, right to the # valid dimensions of the label image tltemp = [0, 0, 0] trtemp = [0, 0, 0] bltemp = [0, 0, 0] brtemp = [0, 0, 0] for i in xrange(3): tltemp[i] = int(round(tlIJKtemp[i])) if tltemp[i] < 0: tltemp[i] = 0 if tltemp[i] >= dims[i]: tltemp[i] = dims[i] - 1 trtemp[i] = int(round(trIJKtemp[i])) if trtemp[i] < 0: trtemp[i] = 0 if trtemp[i] > dims[i]: trtemp[i] = dims[i] - 1 bltemp[i] = int(round(blIJKtemp[i])) if bltemp[i] < 0: bltemp[i] = 0 if bltemp[i] > dims[i]: bltemp[i] = dims[i] - 1 brtemp[i] = int(round(brIJKtemp[i])) if brtemp[i] < 0: brtemp[i] = 0 if brtemp[i] > dims[i]: brtemp[i] = dims[i] - 1 self.painter.SetTopLeft(tltemp[0], tltemp[1], tltemp[2]) self.painter.SetTopRight(trtemp[0], trtemp[1], trtemp[2]) self.painter.SetBottomLeft(bltemp[0], bltemp[1], bltemp[2]) self.painter.SetBottomRight(brtemp[0], brtemp[1], brtemp[2]) self.painter.Paint() # paint the slice: same for circular and spherical brush modes self.painter.SetTopLeft(tl[0], tl[1], tl[2]) self.painter.SetTopRight(tr[0], tr[1], tr[2]) self.painter.SetBottomLeft(bl[0], bl[1], bl[2]) self.painter.SetBottomRight(br[0], br[1], br[2]) self.painter.SetBrushCenter(brushCenter[0], brushCenter[1], brushCenter[2]) self.painter.SetBrushRadius(brushRadius) self.painter.Paint()
def applyImageMask(self, maskIJKToRAS, mask, bounds): """ apply a pre-rasterized image to the current label layer - maskIJKToRAS tells the mapping from image pixels to RAS - mask is a vtkImageData - bounds are the xy extents of the mask (zlo and zhi ignored) """ backgroundLogic = self.sliceLogic.GetBackgroundLayer() backgroundNode = backgroundLogic.GetVolumeNode() if not backgroundNode: return backgroundImage = backgroundNode.GetImageData() if not backgroundImage: return labelLogic = self.sliceLogic.GetLabelLayer() labelNode = labelLogic.GetVolumeNode() if not labelNode: return labelImage = labelNode.GetImageData() if not labelImage: return # # at this point, the mask vtkImageData contains a rasterized # version of the polygon and now needs to be added to the label # image # # store a backup copy of the label map for undo # (this happens in it's own thread, so it is cheap) if self.undoRedo: self.undoRedo.saveState() # # get the mask bounding box in ijk coordinates # - get the xy bounds # - transform to ijk # - clamp the bounds to the dimensions of the label image # xlo, xhi, ylo, yhi, zlo, zhi = bounds labelLogic = self.sliceLogic.GetLabelLayer() xyToIJK = labelLogic.GetXYToIJKTransform() tlIJK = xyToIJK.TransformDoublePoint((xlo, yhi, 0)) trIJK = xyToIJK.TransformDoublePoint((xhi, yhi, 0)) blIJK = xyToIJK.TransformDoublePoint((xlo, ylo, 0)) brIJK = xyToIJK.TransformDoublePoint((xhi, ylo, 0)) # do the clamping of the four corners dims = labelImage.GetDimensions() tl = [ 0, ] * 3 tr = [ 0, ] * 3 bl = [ 0, ] * 3 br = [ 0, ] * 3 corners = ((tlIJK, tl), (trIJK, tr), (blIJK, bl), (brIJK, br)) for corner, clampedCorner in corners: for d in xrange(3): clamped = int(round(corner[d])) if clamped < 0: clamped = 0 if clamped >= dims[d]: clamped = dims[d] - 1 clampedCorner[d] = clamped # # get the ijk to ras matrices including transforms # (use the maskToRAS calculated above) # labelLogic = self.sliceLogic.GetLabelLayer() labelNode = labelLogic.GetVolumeNode() backgroundLogic = self.sliceLogic.GetLabelLayer() backgroundNode = backgroundLogic.GetVolumeNode() backgroundIJKToRAS = self.getIJKToRASMatrix(backgroundNode) labelIJKToRAS = self.getIJKToRASMatrix(labelNode) # # create an extract image for undo if it doesn't exist yet. # if not self.extractImage: self.extractImage = vtk.vtkImageData() parameterNode = EditUtil.getParameterNode() paintLabel = int(parameterNode.GetParameter("label")) paintOver = int(parameterNode.GetParameter("LabelEffect,paintOver")) paintThreshold = int( parameterNode.GetParameter("LabelEffect,paintThreshold")) paintThresholdMin = float( parameterNode.GetParameter("LabelEffect,paintThresholdMin")) paintThresholdMax = float( parameterNode.GetParameter("LabelEffect,paintThresholdMax")) # # set up the painter class and let 'r rip! # self.painter.SetBackgroundImage(backgroundImage) self.painter.SetBackgroundIJKToWorld(backgroundIJKToRAS) self.painter.SetWorkingImage(labelImage) self.painter.SetWorkingIJKToWorld(labelIJKToRAS) self.painter.SetMaskImage(mask) self.painter.SetExtractImage(self.extractImage) self.painter.SetReplaceImage(None) self.painter.SetMaskIJKToWorld(maskIJKToRAS) self.painter.SetTopLeft(tl) self.painter.SetTopRight(tr) self.painter.SetBottomLeft(bl) self.painter.SetBottomRight(br) self.painter.SetPaintLabel(paintLabel) self.painter.SetPaintOver(paintOver) self.painter.SetThresholdPaint(paintThreshold) self.painter.SetThresholdPaintRange(paintThresholdMin, paintThresholdMax) self.painter.Paint() EditUtil.markVolumeNodeAsModified(labelNode)
def __init__(self, parent=None, optionsFrame=None): VTKObservationMixin.__init__(self) self.effects = [] self.effectButtons = {} self.effectCursors = {} self.effectActionGroup = qt.QActionGroup(parent) self.effectActionGroup.connect('triggered(QAction*)', self._onEffectActionTriggered) self.effectActionGroup.exclusive = True self.currentEffect = None self.undoRedo = UndoRedo() self.undoRedo.stateChangedCallback = self.updateUndoRedoButtons self.toggleShortcut = None # check for extensions - if none have been registered, just create the empty dictionary try: slicer.modules.editorExtensions except AttributeError: slicer.modules.editorExtensions = {} # register the builtin extensions self.editorBuiltins = {} self.editorBuiltins["PaintEffect"] = EditorLib.PaintEffect self.editorBuiltins["DrawEffect"] = EditorLib.DrawEffect self.editorBuiltins["ThresholdEffect"] = EditorLib.ThresholdEffect self.editorBuiltins["RectangleEffect"] = EditorLib.RectangleEffect self.editorBuiltins[ "LevelTracingEffect"] = EditorLib.LevelTracingEffect self.editorBuiltins["MakeModelEffect"] = EditorLib.MakeModelEffect self.editorBuiltins["ErodeEffect"] = EditorLib.ErodeEffect self.editorBuiltins["DilateEffect"] = EditorLib.DilateEffect self.editorBuiltins["ChangeLabelEffect"] = EditorLib.ChangeLabelEffect self.editorBuiltins[ "RemoveIslandsEffect"] = EditorLib.RemoveIslandsEffect self.editorBuiltins[ "IdentifyIslandsEffect"] = EditorLib.IdentifyIslandsEffect self.editorBuiltins["SaveIslandEffect"] = EditorLib.SaveIslandEffect self.editorBuiltins[ "ChangeIslandEffect"] = EditorLib.ChangeIslandEffect self.editorBuiltins["GrowCutEffect"] = EditorLib.GrowCutEffect self.editorBuiltins[ "WatershedFromMarkerEffect"] = EditorLib.WatershedFromMarkerEffect self.editorBuiltins[ "FastMarchingEffect"] = EditorLib.FastMarchingEffect self.editorBuiltins["WandEffect"] = EditorLib.WandEffect # frame that holds widgets specific for each effect if not optionsFrame: self.optionsFrame = qt.QFrame(self.parent) self.optionsFrame.objectName = 'OptionsFrame' else: self.optionsFrame = optionsFrame # state variables for selected effect in the box # - currentOption is an instance of an option GUI # - currentTools is a list of EffectTool instances self.currentOption = None self.currentTools = [] # listen for changes in the Interaction Mode interactionNode = slicer.app.applicationLogic().GetInteractionNode() self.addObserver(interactionNode, interactionNode.InteractionModeChangedEvent, self.onInteractionModeChanged) # Listen for changed on the Parameter node self.addObserver(EditUtil.getParameterNode(), vtk.vtkCommand.ModifiedEvent, self._onParameterNodeModified) if not parent: self.parent = qt.QFrame() self.parent.setLayout(qt.QVBoxLayout()) self.create() self.parent.show() else: self.parent = parent self.create()
def __init__(self, parent=None, optionsFrame=None): VTKObservationMixin.__init__(self) self.effects = [] self.effectButtons = {} self.effectCursors = {} self.effectActionGroup = qt.QActionGroup(parent) self.effectActionGroup.connect('triggered(QAction*)', self._onEffectActionTriggered) self.effectActionGroup.exclusive = True self.currentEffect = None self.undoRedo = UndoRedo() self.undoRedo.stateChangedCallback = self.updateUndoRedoButtons self.toggleShortcut = None # check for extensions - if none have been registered, just create the empty dictionary try: slicer.modules.editorExtensions except AttributeError: slicer.modules.editorExtensions = {} # register the builtin extensions self.editorBuiltins = {} self.editorBuiltins["PaintEffect"] = EditorLib.PaintEffect self.editorBuiltins["DrawEffect"] = EditorLib.DrawEffect self.editorBuiltins["ThresholdEffect"] = EditorLib.ThresholdEffect self.editorBuiltins["RectangleEffect"] = EditorLib.RectangleEffect self.editorBuiltins["LevelTracingEffect"] = EditorLib.LevelTracingEffect self.editorBuiltins["MakeModelEffect"] = EditorLib.MakeModelEffect self.editorBuiltins["ErodeEffect"] = EditorLib.ErodeEffect self.editorBuiltins["DilateEffect"] = EditorLib.DilateEffect self.editorBuiltins["ChangeLabelEffect"] = EditorLib.ChangeLabelEffect self.editorBuiltins["RemoveIslandsEffect"] = EditorLib.RemoveIslandsEffect self.editorBuiltins["IdentifyIslandsEffect"] = EditorLib.IdentifyIslandsEffect self.editorBuiltins["SaveIslandEffect"] = EditorLib.SaveIslandEffect self.editorBuiltins["ChangeIslandEffect"] = EditorLib.ChangeIslandEffect self.editorBuiltins["GrowCutEffect"] = EditorLib.GrowCutEffect self.editorBuiltins["WatershedFromMarkerEffect"] = EditorLib.WatershedFromMarkerEffect self.editorBuiltins["FastMarchingEffect"] = EditorLib.FastMarchingEffect self.editorBuiltins["WandEffect"] = EditorLib.WandEffect # frame that holds widgets specific for each effect if not optionsFrame: self.optionsFrame = qt.QFrame(self.parent) self.optionsFrame.objectName = 'OptionsFrame' else: self.optionsFrame = optionsFrame # state variables for selected effect in the box # - currentOption is an instance of an option GUI # - currentTools is a list of EffectTool instances self.currentOption = None self.currentTools = [] # listen for changes in the Interaction Mode interactionNode = slicer.app.applicationLogic().GetInteractionNode() self.addObserver(interactionNode, interactionNode.InteractionModeChangedEvent, self.onInteractionModeChanged) # Listen for changed on the Parameter node self.addObserver(EditUtil.getParameterNode(), vtk.vtkCommand.ModifiedEvent, self._onParameterNodeModified) if not parent: self.parent = qt.QFrame() self.parent.setLayout( qt.QVBoxLayout() ) self.create() self.parent.show() else: self.parent = parent self.create()
def paintBrush(self, x, y): """ paint with a brush that is circular (or optionally spherical) in XY space (could be streched or rotate when transformed to IJK) - make sure to hit every pixel in IJK space - apply the threshold if selected """ sliceLogic = self.sliceWidget.sliceLogic() sliceNode = sliceLogic.GetSliceNode() labelLogic = sliceLogic.GetLabelLayer() labelNode = labelLogic.GetVolumeNode() labelImage = labelNode.GetImageData() backgroundLogic = sliceLogic.GetBackgroundLayer() backgroundNode = backgroundLogic.GetVolumeNode() backgroundImage = backgroundNode.GetImageData() if not labelNode: # if there's no label, we can't paint return # # get the brush bounding box in ijk coordinates # - get the xy bounds # - transform to ijk # - clamp the bounds to the dimensions of the label image # bounds = self.brush.GetPoints().GetBounds() left = x + bounds[0] right = x + bounds[1] bottom = y + bounds[2] top = y + bounds[3] xyToIJK = labelLogic.GetXYToIJKTransform() tlIJK = xyToIJK.TransformDoublePoint( (left, top, 0) ) trIJK = xyToIJK.TransformDoublePoint( (right, top, 0) ) blIJK = xyToIJK.TransformDoublePoint( (left, bottom, 0) ) brIJK = xyToIJK.TransformDoublePoint( (right, bottom, 0) ) dims = labelImage.GetDimensions() # clamp the top, bottom, left, right to the # valid dimensions of the label image tl = [0,0,0] tr = [0,0,0] bl = [0,0,0] br = [0,0,0] for i in xrange(3): tl[i] = int(round(tlIJK[i])) if tl[i] < 0: tl[i] = 0 if tl[i] >= dims[i]: tl[i] = dims[i] - 1 tr[i] = int(round(trIJK[i])) if tr[i] < 0: tr[i] = 0 if tr[i] > dims[i]: tr[i] = dims[i] - 1 bl[i] = int(round(blIJK[i])) if bl[i] < 0: bl[i] = 0 if bl[i] > dims[i]: bl[i] = dims[i] - 1 br[i] = int(round(brIJK[i])) if br[i] < 0: br[i] = 0 if br[i] > dims[i]: br[i] = dims[i] - 1 # If the region is smaller than a pixel then paint it using paintPixel mode, # to make sure at least one pixel is filled on each click maxRowDelta = 0 maxColumnDelta = 0 for i in xrange(3): d = abs(tr[i] - tl[i]) if d > maxColumnDelta: maxColumnDelta = d d = abs(br[i] - bl[i]) if d > maxColumnDelta: maxColumnDelta = d d = abs(bl[i] - tl[i]) if d > maxRowDelta: maxRowDelta = d d = abs(br[i] - tr[i]) if d > maxRowDelta: maxRowDelta = d if maxRowDelta<=1 or maxColumnDelta<=1 : self.paintPixel(x,y) return # # get the layers and nodes # and ijk to ras matrices including transforms # labelLogic = self.sliceLogic.GetLabelLayer() labelNode = labelLogic.GetVolumeNode() backgroundLogic = self.sliceLogic.GetLabelLayer() backgroundNode = backgroundLogic.GetVolumeNode() backgroundIJKToRAS = self.logic.getIJKToRASMatrix(backgroundNode) labelIJKToRAS = self.logic.getIJKToRASMatrix(labelNode) xyToRAS = sliceNode.GetXYToRAS() brushCenter = xyToRAS.MultiplyPoint( (x, y, 0, 1) )[:3] brushRadius = self.radius bSphere = self.sphere parameterNode = EditUtil.getParameterNode() paintLabel = int(parameterNode.GetParameter("label")) paintOver = int(parameterNode.GetParameter("LabelEffect,paintOver")) paintThreshold = int(parameterNode.GetParameter("LabelEffect,paintThreshold")) paintThresholdMin = float( parameterNode.GetParameter("LabelEffect,paintThresholdMin")) paintThresholdMax = float( parameterNode.GetParameter("LabelEffect,paintThresholdMax")) # # set up the painter class and let 'r rip! # if not hasattr(self,"painter"): self.painter = slicer.vtkImageSlicePaint() self.painter.SetBackgroundImage(backgroundImage) self.painter.SetBackgroundIJKToWorld(backgroundIJKToRAS) self.painter.SetWorkingImage(labelImage) self.painter.SetWorkingIJKToWorld(labelIJKToRAS) self.painter.SetTopLeft( tl[0], tl[1], tl[2] ) self.painter.SetTopRight( tr[0], tr[1], tr[2] ) self.painter.SetBottomLeft( bl[0], bl[1], bl[2] ) self.painter.SetBottomRight( br[0], br[1], br[2] ) self.painter.SetBrushCenter( brushCenter[0], brushCenter[1], brushCenter[2] ) self.painter.SetBrushRadius( brushRadius ) self.painter.SetPaintLabel(paintLabel) self.painter.SetPaintOver(paintOver) self.painter.SetThresholdPaint(paintThreshold) self.painter.SetThresholdPaintRange(paintThresholdMin, paintThresholdMax) if bSphere: # fill volume of a sphere rather than a circle on the currently displayed image slice # Algorithm: ########################### # Assume brushRadius is in mm # Estimate zVoxelSize # Compute number of slices spanned by sphere, that are still within the volume # For each spanned slice # reposition the brushCenter using xy(z)ToRAS transform: i.e. canvas to patient world coordinates # resize the radius: brushRadiusOffset=sqrt(brushRadius*brushRadius - zOffset_mm*zOffset_mm) # invoke Paint() # Finally paint on the center slice, leaving the gui on the center slice being most visibly edited #------------------ # Estimate zVoxelSize_mm brushCenter1 = xyToRAS.MultiplyPoint( (x, y, 0, 1) )[:3] brushCenter2 = xyToRAS.MultiplyPoint( (x, y, 100, 1) )[:3] dx1=brushCenter1[0]-brushCenter2[0] dx2=brushCenter1[1]-brushCenter2[1] dx3=brushCenter1[2]-brushCenter2[2] distanceSpannedBy100Slices = sqrt(dx1*dx1+dx2*dx2+dx3*dx3) # compute L2 norm if distanceSpannedBy100Slices==0: zVoxelSize_mm=1 else: zVoxelSize_mm = distanceSpannedBy100Slices/100 # -- # Compute number of slices spanned by sphere nNumSlicesInEachDirection=brushRadius / zVoxelSize_mm; nNumSlicesInEachDirection=nNumSlicesInEachDirection-1 sliceOffsetArray=numpy.concatenate((-1*numpy.arange(1,nNumSlicesInEachDirection+1,), numpy.arange(1,nNumSlicesInEachDirection+1))) for iSliceOffset in sliceOffsetArray: # x,y uses slice (canvas) coordinate system and actually has a 3rd z component (index into the slice you're looking at) # hence xyToRAS is really performing xyzToRAS. RAS is patient world coordinate system. Note the 1 is because the trasform uses homogeneous coordinates iBrushCenter = xyToRAS.MultiplyPoint( (x, y, iSliceOffset, 1) )[:3] self.painter.SetBrushCenter( iBrushCenter[0], iBrushCenter[1], iBrushCenter[2] ) # [ ] Need to just continue (pass this loop iteration if the brush center is not within the volume zOffset_mm=zVoxelSize_mm*iSliceOffset; brushRadiusOffset=sqrt(brushRadius*brushRadius - zOffset_mm*zOffset_mm) self.painter.SetBrushRadius( brushRadiusOffset ) # -- tlIJKtemp = xyToIJK.TransformDoublePoint( (left, top, iSliceOffset) ) trIJKtemp = xyToIJK.TransformDoublePoint( (right, top, iSliceOffset) ) blIJKtemp = xyToIJK.TransformDoublePoint( (left, bottom, iSliceOffset) ) brIJKtemp = xyToIJK.TransformDoublePoint( (right, bottom, iSliceOffset) ) # clamp the top, bottom, left, right to the # valid dimensions of the label image tltemp = [0,0,0] trtemp = [0,0,0] bltemp = [0,0,0] brtemp = [0,0,0] for i in xrange(3): tltemp[i] = int(round(tlIJKtemp[i])) if tltemp[i] < 0: tltemp[i] = 0 if tltemp[i] >= dims[i]: tltemp[i] = dims[i] - 1 trtemp[i] = int(round(trIJKtemp[i])) if trtemp[i] < 0: trtemp[i] = 0 if trtemp[i] > dims[i]: trtemp[i] = dims[i] - 1 bltemp[i] = int(round(blIJKtemp[i])) if bltemp[i] < 0: bltemp[i] = 0 if bltemp[i] > dims[i]: bltemp[i] = dims[i] - 1 brtemp[i] = int(round(brIJKtemp[i])) if brtemp[i] < 0: brtemp[i] = 0 if brtemp[i] > dims[i]: brtemp[i] = dims[i] - 1 self.painter.SetTopLeft( tltemp[0], tltemp[1], tltemp[2] ) self.painter.SetTopRight( trtemp[0], trtemp[1], trtemp[2] ) self.painter.SetBottomLeft( bltemp[0], bltemp[1], bltemp[2] ) self.painter.SetBottomRight( brtemp[0], brtemp[1], brtemp[2] ) self.painter.Paint() # paint the slice: same for circular and spherical brush modes self.painter.SetTopLeft( tl[0], tl[1], tl[2] ) self.painter.SetTopRight( tr[0], tr[1], tr[2] ) self.painter.SetBottomLeft( bl[0], bl[1], bl[2] ) self.painter.SetBottomRight( br[0], br[1], br[2] ) self.painter.SetBrushCenter( brushCenter[0], brushCenter[1], brushCenter[2] ) self.painter.SetBrushRadius( brushRadius ) self.painter.Paint()
def applyImageMask(self, maskIJKToRAS, mask, bounds): """ apply a pre-rasterized image to the current label layer - maskIJKToRAS tells the mapping from image pixels to RAS - mask is a vtkImageData - bounds are the xy extents of the mask (zlo and zhi ignored) """ backgroundLogic = self.sliceLogic.GetBackgroundLayer() backgroundNode = backgroundLogic.GetVolumeNode() if not backgroundNode: return backgroundImage = backgroundNode.GetImageData() if not backgroundImage: return labelLogic = self.sliceLogic.GetLabelLayer() labelNode = labelLogic.GetVolumeNode() if not labelNode: return labelImage = labelNode.GetImageData() if not labelImage: return # # at this point, the mask vtkImageData contains a rasterized # version of the polygon and now needs to be added to the label # image # # store a backup copy of the label map for undo # (this happens in it's own thread, so it is cheap) if self.undoRedo: self.undoRedo.saveState() # # get the mask bounding box in ijk coordinates # - get the xy bounds # - transform to ijk # - clamp the bounds to the dimensions of the label image # xlo, xhi, ylo, yhi, zlo, zhi = bounds labelLogic = self.sliceLogic.GetLabelLayer() xyToIJK = labelLogic.GetXYToIJKTransform() tlIJK = xyToIJK.TransformDoublePoint( (xlo, yhi, 0) ) trIJK = xyToIJK.TransformDoublePoint( (xhi, yhi, 0) ) blIJK = xyToIJK.TransformDoublePoint( (xlo, ylo, 0) ) brIJK = xyToIJK.TransformDoublePoint( (xhi, ylo, 0) ) # do the clamping of the four corners dims = labelImage.GetDimensions() tl = [0,] * 3 tr = [0,] * 3 bl = [0,] * 3 br = [0,] * 3 corners = ((tlIJK, tl),(trIJK, tr),(blIJK, bl),(brIJK, br)) for corner,clampedCorner in corners: for d in xrange(3): clamped = int(round(corner[d])) if clamped < 0: clamped = 0 if clamped >= dims[d]: clamped = dims[d]-1 clampedCorner[d] = clamped # # get the ijk to ras matrices including transforms # (use the maskToRAS calculated above) # labelLogic = self.sliceLogic.GetLabelLayer() labelNode = labelLogic.GetVolumeNode() backgroundLogic = self.sliceLogic.GetLabelLayer() backgroundNode = backgroundLogic.GetVolumeNode() backgroundIJKToRAS = self.getIJKToRASMatrix(backgroundNode) labelIJKToRAS = self.getIJKToRASMatrix(labelNode) # # create an exract image for undo if it doesn't exist yet. # if not self.extractImage: self.extractImage = vtk.vtkImageData() parameterNode = EditUtil.getParameterNode() paintLabel = int(parameterNode.GetParameter("label")) paintOver = int(parameterNode.GetParameter("LabelEffect,paintOver")) paintThreshold = int(parameterNode.GetParameter("LabelEffect,paintThreshold")) paintThresholdMin = float( parameterNode.GetParameter("LabelEffect,paintThresholdMin")) paintThresholdMax = float( parameterNode.GetParameter("LabelEffect,paintThresholdMax")) # # set up the painter class and let 'r rip! # self.painter.SetBackgroundImage( backgroundImage ) self.painter.SetBackgroundIJKToWorld( backgroundIJKToRAS ) self.painter.SetWorkingImage( labelImage ) self.painter.SetWorkingIJKToWorld( labelIJKToRAS ) self.painter.SetMaskImage( mask ) self.painter.SetExtractImage( self.extractImage ) self.painter.SetReplaceImage(None) self.painter.SetMaskIJKToWorld( maskIJKToRAS ) self.painter.SetTopLeft(tl) self.painter.SetTopRight(tr) self.painter.SetBottomLeft(bl) self.painter.SetBottomRight(br) self.painter.SetPaintLabel( paintLabel ) self.painter.SetPaintOver( paintOver ) self.painter.SetThresholdPaint( paintThreshold ) self.painter.SetThresholdPaintRange( paintThresholdMin, paintThresholdMax ) self.painter.Paint() EditUtil.markVolumeNodeAsModified(labelNode)