Slicer 4.2
Slicer is a multi-platform, free and open source software package for visualization and medical image computing
PaintEffect.py
Go to the documentation of this file.
00001 import os
00002 from __main__ import vtk
00003 from __main__ import ctk
00004 from __main__ import qt
00005 from __main__ import slicer
00006 from EditOptions import EditOptions
00007 from EditorLib import EditorLib
00008 import LabelEffect
00009 
00010 
00011 #########################################################
00012 #
00013 # 
00014 comment = """
00015 
00016   PaintEffect is a subclass of LabelEffect
00017   that implements the interactive paintbrush tool
00018   in the slicer editor
00019 
00020 # TODO : 
00021 """
00022 #
00023 #########################################################
00024 
00025 #
00026 # PaintEffectOptions - see LabelEffect, EditOptions and Effect for superclasses
00027 #
00028 
00029 class PaintEffectOptions(LabelEffect.LabelEffectOptions):
00030   """ PaintEffect-specfic gui
00031   """
00032 
00033   def __init__(self, parent=0):
00034     super(PaintEffectOptions,self).__init__(parent)
00035     # option to use 'min' or 'diag'
00036     # - min means pixel radius is min spacing
00037     # - diag means corner to corner length
00038     self.radiusPixelMode = 'min'
00039 
00040   def __del__(self):
00041     super(PaintEffectOptions,self).__del__()
00042 
00043   def create(self):
00044     super(PaintEffectOptions,self).create()
00045 
00046     labelVolume = self.editUtil.getLabelVolume()
00047     if labelVolume and labelVolume.GetImageData():
00048       spacing = labelVolume.GetSpacing()
00049       dimensions = labelVolume.GetImageData().GetDimensions()
00050       self.minimumRadius = 0.5 * min(spacing)
00051       bounds = [a*b for a,b in zip(spacing,dimensions)]
00052       self.maximumRadius = 0.5 * max(bounds)
00053     else:
00054       self.minimumRadius = 0.01
00055       self.maximumRadius = 100
00056 
00057     self.radiusFrame = qt.QFrame(self.frame)
00058     self.radiusFrame.setLayout(qt.QHBoxLayout())
00059     self.frame.layout().addWidget(self.radiusFrame)
00060     self.widgets.append(self.radiusFrame)
00061     self.radiusLabel = qt.QLabel("Radius:", self.radiusFrame)
00062     self.radiusLabel.setToolTip("Set the radius of the paint brush in millimeters")
00063     self.radiusFrame.layout().addWidget(self.radiusLabel)
00064     self.widgets.append(self.radiusLabel)
00065     self.radiusSpinBox = ctk.ctkSpinBox(self.radiusFrame)
00066     self.radiusSpinBox.setToolTip("Set the radius of the paint brush in millimeters")
00067     self.radiusSpinBox.minimum = self.minimumRadius
00068     self.radiusSpinBox.maximum = self.maximumRadius
00069     self.radiusSpinBox.suffix = "mm"
00070     from math import log,floor
00071     decimals = floor(log(self.minimumRadius,10))
00072     if decimals < 0:
00073       self.radiusSpinBox.decimals = -decimals + 2
00074     self.radiusFrame.layout().addWidget(self.radiusSpinBox)
00075     self.widgets.append(self.radiusSpinBox)
00076     self.radiusUnitsToggle = qt.QPushButton("px:")
00077     self.radiusUnitsToggle.setToolTip("Toggle radius quick set buttons between mm and label volume pixel size units")
00078     self.radiusUnitsToggle.setFixedWidth(35)
00079     self.radiusFrame.layout().addWidget(self.radiusUnitsToggle)
00080     self.radiusUnitsToggle.connect('clicked()',self.onRadiusUnitsToggle)
00081     self.radiusQuickies = {}
00082     quickies = ( (2, self.onQuickie2Clicked), (3,self.onQuickie3Clicked),
00083                  (4, self.onQuickie4Clicked), (5, self.onQuickie5Clicked),
00084                  (10, self.onQuickie10Clicked), (20, self.onQuickie20Clicked) )
00085     for rad,callback in quickies:
00086       self.radiusQuickies[rad] = qt.QPushButton(str(rad))
00087       self.radiusFrame.layout().addWidget(self.radiusQuickies[rad])
00088       self.radiusQuickies[rad].setFixedWidth(25)
00089       self.radiusQuickies[rad].connect('clicked()', callback)
00090       self.radiusQuickies[rad].setToolTip("Set radius based on mm or label voxel size units depending on toggle value")
00091 
00092     self.radius = ctk.ctkDoubleSlider(self.frame)
00093     self.radius.minimum = self.minimumRadius
00094     self.radius.maximum = self.maximumRadius
00095     self.radius.orientation = 1
00096     self.radius.singleStep = self.minimumRadius
00097     self.frame.layout().addWidget(self.radius)
00098     self.widgets.append(self.radius)
00099 
00100     self.smudge = qt.QCheckBox("Smudge", self.frame)
00101     self.smudge.setToolTip("Set the label number automatically by sampling the pixel location where the brush stroke starts.")
00102     self.frame.layout().addWidget(self.smudge)
00103     self.widgets.append(self.smudge)
00104 
00105     self.pixelMode = qt.QCheckBox("Pixel Mode", self.frame)
00106     self.pixelMode.setToolTip("Paint exactly the pixel under the cursor, ignoring the radius, threshold, and paint over.")
00107     self.frame.layout().addWidget(self.pixelMode)
00108     self.widgets.append(self.pixelMode)
00109 
00110     EditorLib.HelpButton(self.frame, "Use this tool to paint with a round brush of the selected radius")
00111 
00112     self.connections.append( (self.smudge, 'clicked()', self.updateMRMLFromGUI) )
00113     self.connections.append( (self.pixelMode, 'clicked()', self.updateMRMLFromGUI) )
00114     self.connections.append( (self.radius, 'valueChanged(double)', self.onRadiusValueChanged) )
00115     self.connections.append( (self.radiusSpinBox, 'valueChanged(double)', self.onRadiusSpinBoxChanged) )
00116 
00117     # Add vertical spacer
00118     self.frame.layout().addStretch(1)
00119 
00120     # set the node parameters that are dependent on the input data
00121     self.parameterNode.SetParameter( "PaintEffect,radius", str(self.minimumRadius * 10) )
00122 
00123   def destroy(self):
00124     super(PaintEffectOptions,self).destroy()
00125 
00126   # note: this method needs to be implemented exactly as-is
00127   # in each leaf subclass so that "self" in the observer
00128   # is of the correct type 
00129   def updateParameterNode(self, caller, event):
00130     node = self.editUtil.getParameterNode()
00131     if node != self.parameterNode:
00132       if self.parameterNode:
00133         node.RemoveObserver(self.parameterNodeTag)
00134       self.parameterNode = node
00135       self.parameterNodeTag = node.AddObserver(vtk.vtkCommand.ModifiedEvent, self.updateGUIFromMRML)
00136 
00137   def setMRMLDefaults(self):
00138     super(PaintEffectOptions,self).setMRMLDefaults()
00139     disableState = self.parameterNode.GetDisableModifiedEvent()
00140     self.parameterNode.SetDisableModifiedEvent(1)
00141     defaults = (
00142       ("radius", "5"),
00143       ("smudge", "0"),
00144       ("pixelMode", "0"),
00145     )
00146     for d in defaults:
00147       param = "PaintEffect,"+d[0]
00148       pvalue = self.parameterNode.GetParameter(param)
00149       if pvalue == '':
00150         self.parameterNode.SetParameter(param, d[1])
00151     self.parameterNode.SetDisableModifiedEvent(disableState)
00152 
00153   def updateGUIFromMRML(self,caller,event):
00154     params = ("radius", "smudge", "pixelMode")
00155     for p in params:
00156       if self.parameterNode.GetParameter("PaintEffect,"+p) == '':
00157         # don't update if the parameter node has not got all values yet
00158         return
00159     super(PaintEffectOptions,self).updateGUIFromMRML(caller,event)
00160     self.disconnectWidgets()
00161     smudge = not (0 == int(self.parameterNode.GetParameter("PaintEffect,smudge")))
00162     self.smudge.setChecked( smudge )
00163     pixelMode = not (0 == int(self.parameterNode.GetParameter("PaintEffect,pixelMode")))
00164     self.pixelMode.setChecked( pixelMode )
00165     self.radiusFrame.enabled = not pixelMode
00166     self.threshold.enabled = not pixelMode
00167     self.thresholdPaint.enabled = not pixelMode
00168     self.thresholdLabel.enabled = not pixelMode
00169     self.paintOver.enabled = not pixelMode
00170     radius = float(self.parameterNode.GetParameter("PaintEffect,radius"))
00171     self.radius.setValue( radius )
00172     self.radiusSpinBox.setValue( radius )
00173     for tool in self.tools:
00174       tool.smudge = smudge
00175       tool.pixelMode = pixelMode
00176       tool.radius = radius
00177       tool.createGlyph(tool.brush)
00178     self.connectWidgets()
00179 
00180   def onRadiusUnitsToggle(self):
00181     if self.radiusUnitsToggle.text == 'mm:':
00182       self.radiusUnitsToggle.text = 'px:'
00183     else:
00184       self.radiusUnitsToggle.text = 'mm:'
00185   def onQuickie2Clicked(self):
00186     self.setQuickieRadius(2)
00187   def onQuickie3Clicked(self):
00188     self.setQuickieRadius(3)
00189   def onQuickie4Clicked(self):
00190     self.setQuickieRadius(4)
00191   def onQuickie5Clicked(self):
00192     self.setQuickieRadius(5)
00193   def onQuickie10Clicked(self):
00194     self.setQuickieRadius(10)
00195   def onQuickie20Clicked(self):
00196     self.setQuickieRadius(20)
00197 
00198   def setQuickieRadius(self,radius):
00199     labelVolume = self.editUtil.getLabelVolume()
00200     if labelVolume:
00201       if self.radiusUnitsToggle.text == 'px:':
00202         spacing = labelVolume.GetSpacing()
00203         if self.radiusPixelMode == 'diag':
00204           from math import sqrt
00205           diag = sqrt(reduce(lambda x,y:x+y, map(lambda x: x**2, spacing)))
00206           mmRadius = diag * radius
00207         elif self.radiusPixelMode == 'min':
00208           mmRadius = min(spacing) * radius
00209         else:
00210           print (self,"Unknown pixel mode - using 5mm")
00211           mmRadius = 5
00212       else:
00213         mmRadius = radius
00214       self.disconnectWidgets()
00215       self.radiusSpinBox.setValue(mmRadius)
00216       self.radius.setValue(mmRadius)
00217       self.connectWidgets()
00218       self.updateMRMLFromGUI()
00219 
00220   def onRadiusValueChanged(self,value):
00221     self.radiusSpinBox.setValue(self.radius.value)
00222     self.updateMRMLFromGUI()
00223 
00224   def onRadiusSpinBoxChanged(self,value):
00225     self.radius.setValue(self.radiusSpinBox.value)
00226     self.updateMRMLFromGUI()
00227 
00228   def updateMRMLFromGUI(self):
00229     disableState = self.parameterNode.GetDisableModifiedEvent()
00230     self.parameterNode.SetDisableModifiedEvent(1)
00231     super(PaintEffectOptions,self).updateMRMLFromGUI()
00232     if self.smudge.checked:
00233       self.parameterNode.SetParameter( "PaintEffect,smudge", "1" )
00234     else:
00235       self.parameterNode.SetParameter( "PaintEffect,smudge", "0" )
00236     if self.pixelMode.checked:
00237       self.parameterNode.SetParameter( "PaintEffect,pixelMode", "1" )
00238     else:
00239       self.parameterNode.SetParameter( "PaintEffect,pixelMode", "0" )
00240     self.parameterNode.SetParameter( "PaintEffect,radius", str(self.radius.value) )
00241     self.parameterNode.SetDisableModifiedEvent(disableState)
00242     if not disableState:
00243       self.parameterNode.InvokePendingModifiedEvent()
00244 
00245 #
00246 # PaintEffectTool
00247 #
00248  
00249 class PaintEffectTool(LabelEffect.LabelEffectTool):
00250   """
00251   One instance of this will be created per-view when the effect
00252   is selected.  It is responsible for implementing feedback and
00253   label map changes in response to user input.
00254   This class observes the editor parameter node to configure itself
00255   and queries the current view for background and label volume
00256   nodes to operate on.
00257   """
00258 
00259   def __init__(self, sliceWidget):
00260     super(PaintEffectTool,self).__init__(sliceWidget)
00261     # create a logic instance to do the non-gui work
00262     self.logic = PaintEffectLogic(self.sliceWidget.sliceLogic())
00263 
00264     # configuration variables
00265     self.delayedPaint = True
00266     self.parameterNode = self.editUtil.getParameterNode()
00267     self.smudge = not (0 == int(self.parameterNode.GetParameter("PaintEffect,smudge")))
00268     self.pixelMode = not (0 == int(self.parameterNode.GetParameter("PaintEffect,pixelMode")))
00269     self.radius = float(self.parameterNode.GetParameter("PaintEffect,radius"))
00270 
00271     # interaction state variables
00272     self.position = [0, 0, 0]
00273     self.paintCoordinates = []
00274     self.feedbackActors = []
00275     self.lastRadius = 0
00276 
00277     # scratch variables
00278     self.rasToXY = vtk.vtkMatrix4x4()
00279 
00280     # initialization
00281     self.brush = vtk.vtkPolyData()
00282     self.createGlyph(self.brush)
00283     self.mapper = vtk.vtkPolyDataMapper2D()
00284     self.actor = vtk.vtkActor2D()
00285     self.mapper.SetInput(self.brush)
00286     self.actor.SetMapper(self.mapper)
00287     self.actor.VisibilityOff()
00288 
00289     self.renderer.AddActor2D(self.actor)
00290     self.actors.append(self.actor)
00291 
00292     self.processEvent()
00293 
00294   def cleanup(self):
00295     """
00296     Remove actors from renderer and call superclass
00297     """
00298     for a in self.feedbackActors:
00299       self.renderer.RemoveActor2D(a)
00300     self.sliceView.scheduleRender()
00301     super(PaintEffectTool,self).cleanup()
00302 
00303   def getLabelPixel(self,xy):
00304     sliceLogic = self.sliceWidget.sliceLogic()
00305     labelLogic = sliceLogic.GetLabelLayer()
00306     xyToIJK = labelLogic.GetXYToIJKTransform().GetMatrix()
00307     i,j,k,l = xyToIJK.MultiplyPoint( xy + (0, 1) )
00308     i = int(round(i))
00309     j = int(round(j))
00310     k = int(round(k))
00311     labelImage = labelLogic.GetVolumeNode().GetImageData()
00312     pixel = int(labelImage.GetScalarComponentAsDouble(i,j,k,0))
00313     return(pixel)
00314     
00315   def scaleRadius(self,scaleFactor):
00316     radius = float(self.parameterNode.GetParameter("PaintEffect,radius"))
00317     self.parameterNode.SetParameter( "PaintEffect,radius", str(radius * scaleFactor) )
00318     
00319 
00320   def processEvent(self, caller=None, event=None):
00321     """
00322     handle events from the render window interactor
00323     """
00324 
00325     if super(PaintEffectTool,self).processEvent(caller,event):
00326       return
00327 
00328     # interactor events
00329     if event == "LeftButtonPressEvent":
00330       self.actionState = "painting"
00331       if not self.pixelMode:
00332         self.cursorOff()
00333       xy = self.interactor.GetEventPosition()
00334       if self.smudge:
00335         self.editUtil.setLabel(self.getLabelPixel(xy))
00336       self.paintAddPoint(xy[0], xy[1])
00337       self.abortEvent(event)
00338     elif event == "LeftButtonReleaseEvent":
00339       self.paintApply()
00340       self.actionState = None
00341       self.cursorOn()
00342     elif event == "MouseMoveEvent":
00343       self.actor.VisibilityOn()
00344       if self.actionState == "painting":
00345         xy = self.interactor.GetEventPosition()
00346         self.paintAddPoint(xy[0], xy[1])
00347         self.abortEvent(event)
00348     elif event == "EnterEvent":
00349       self.actor.VisibilityOn()
00350     elif event == "LeaveEvent":
00351       self.actor.VisibilityOff()
00352     elif event == "KeyPressEvent":
00353       key = self.interactor.GetKeySym()
00354       if key == 'plus' or key == 'equal':
00355         self.scaleRadius(1.2)
00356       if key == 'minus' or key == 'underscore':
00357         self.scaleRadius(0.8)
00358     else:
00359       pass
00360 
00361     # events from the slice node
00362     if caller and caller.IsA('vtkMRMLSliceNode'):
00363       if hasattr(self,'brush'):
00364         self.createGlyph(self.brush)
00365 
00366     self.positionActors()
00367 
00368   def positionActors(self):
00369     """
00370     update paint feedback glyph to follow mouse
00371     """
00372     if hasattr(self,'actor'):
00373       self.actor.SetPosition( self.interactor.GetEventPosition() )
00374       self.sliceView.scheduleRender()
00375     
00376 
00377   def createGlyph(self, polyData):
00378     """
00379     create a brush circle of the right radius in XY space
00380     - assume uniform scaling between XY and RAS which
00381       is enforced by the view interactors
00382     """
00383     sliceNode = self.sliceWidget.sliceLogic().GetSliceNode()
00384     self.rasToXY.DeepCopy(sliceNode.GetXYToRAS())
00385     self.rasToXY.Invert()
00386     if self.rasToXY.GetElement(0, 0) != 0:
00387       point = (self.radius, 0, 0, 0)
00388     else:
00389       point = (0, self.radius,  0, 0)
00390     xyRadius = self.rasToXY.MultiplyPoint(point)
00391     import math
00392     xyRadius = math.sqrt( xyRadius[0]**2 + xyRadius[1]**2 + xyRadius[2]**2 )
00393 
00394     if self.pixelMode:
00395       xyRadius = 0.01
00396 
00397     # make a circle paint brush
00398     points = vtk.vtkPoints()
00399     lines = vtk.vtkCellArray()
00400     polyData.SetPoints(points)
00401     polyData.SetLines(lines)
00402     PI = 3.1415926
00403     TWOPI = PI * 2
00404     PIoverSIXTEEN = PI / 16
00405     prevPoint = -1
00406     firstPoint = -1
00407     angle = 0
00408     while angle <= TWOPI:
00409       x = xyRadius * math.cos(angle)
00410       y = xyRadius * math.sin(angle)
00411       p = points.InsertNextPoint( x, y, 0 )
00412       if prevPoint != -1:
00413         idList = vtk.vtkIdList()
00414         idList.InsertNextId(prevPoint)
00415         idList.InsertNextId(p)
00416         polyData.InsertNextCell( vtk.VTK_LINE, idList )
00417       prevPoint = p
00418       if firstPoint == -1:
00419         firstPoint = p
00420       angle = angle + PIoverSIXTEEN
00421 
00422     # make the last line in the circle
00423     idList = vtk.vtkIdList()
00424     idList.InsertNextId(p)
00425     idList.InsertNextId(firstPoint)
00426     polyData.InsertNextCell( vtk.VTK_LINE, idList )
00427 
00428   def paintAddPoint(self, x, y):
00429     """
00430     depending on the delayedPaint mode, either paint the
00431     given point or queue it up with a marker for later 
00432     painting
00433     """
00434     self.paintCoordinates.append( (x, y) )
00435     if self.delayedPaint and not self.pixelMode:
00436       self.paintFeedback()
00437     else:
00438       self.paintApply()
00439 
00440   def paintFeedback(self):
00441     """
00442     add a feedback actor (copy of the paint radius
00443     actor) for any points that don't have one yet.
00444     If the list is empty, clear out the old actors
00445     """
00446 
00447     if self.paintCoordinates == []:
00448       for a in self.feedbackActors:
00449         self.renderer.RemoveActor2D(a)
00450       self.feedbackActors = []
00451       return
00452 
00453     for xy in self.paintCoordinates[len(self.feedbackActors):]:
00454       a = vtk.vtkActor2D()
00455       self.feedbackActors.append(a)
00456       a.SetMapper(self.mapper)
00457       a.SetPosition(xy[0], xy[1])
00458       property = a.GetProperty()
00459       property.SetColor(.7, .7, 0)
00460       property.SetOpacity( .5 )
00461       self.renderer.AddActor2D( a )
00462 
00463   def paintApply(self):
00464     if self.paintCoordinates != []:
00465       if self.undoRedo:
00466         self.undoRedo.saveState()
00467     
00468     for xy in self.paintCoordinates:
00469       if self.pixelMode:
00470         self.paintPixel(xy[0], xy[1])
00471       else:
00472         self.paintBrush(xy[0], xy[1])
00473     self.paintCoordinates = []
00474     self.paintFeedback()
00475 
00476     # TODO: workaround for new pipeline in slicer4
00477     # - editing image data of the calling modified on the node
00478     #   does not pull the pipeline chain
00479     # - so we trick it by changing the image data first
00480     sliceLogic = self.sliceWidget.sliceLogic()
00481     labelLogic = sliceLogic.GetLabelLayer()
00482     labelNode = labelLogic.GetVolumeNode()
00483     self.editUtil.markVolumeNodeAsModified(labelNode)
00484 
00485   def paintPixel(self, x, y):
00486     """
00487     paint with a single pixel (in label space)
00488     """
00489     sliceLogic = self.sliceWidget.sliceLogic()
00490     labelLogic = sliceLogic.GetLabelLayer()
00491     labelNode = labelLogic.GetVolumeNode()
00492     labelImage = labelNode.GetImageData()
00493 
00494     if not labelNode:
00495       # if there's no label, we can't paint
00496       return
00497 
00498     xyToIJK = labelLogic.GetXYToIJKTransform().GetMatrix()
00499     ijkFloat = xyToIJK.MultiplyPoint( (x, y, 0, 1) )
00500     ijk = []
00501     for e in ijkFloat:
00502       try:
00503         index = int(round(e))
00504       except ValueError:
00505         return
00506       ijk.append(index)
00507     dims = labelImage.GetDimensions()
00508     for e,d in zip(ijk,dims): # clamp to volume extent
00509       if e < 0 or e >= d:
00510         return
00511 
00512     parameterNode = self.editUtil.getParameterNode()
00513     paintLabel = int(parameterNode.GetParameter("label"))
00514     labelImage.SetScalarComponentFromFloat(ijk[0],ijk[1],ijk[2],0, paintLabel)
00515     self.editUtil.markVolumeNodeAsModified(labelNode)
00516 
00517   def paintBrush(self, x, y):
00518     """
00519     paint with a brush that is circular in XY space 
00520      (could be streched or rotate when transformed to IJK)
00521      - make sure to hit ever pixel in IJK space 
00522      - apply the threshold if selected
00523     """
00524     sliceLogic = self.sliceWidget.sliceLogic()
00525     sliceNode = sliceLogic.GetSliceNode()
00526     labelLogic = sliceLogic.GetLabelLayer()
00527     labelNode = labelLogic.GetVolumeNode()
00528     labelImage = labelNode.GetImageData()
00529     backgroundLogic = sliceLogic.GetBackgroundLayer()
00530     backgroundNode = backgroundLogic.GetVolumeNode()
00531     backgroundImage = backgroundNode.GetImageData()
00532 
00533     if not labelNode:
00534       # if there's no label, we can't paint
00535       return
00536 
00537     #
00538     # get the brush bounding box in ijk coordinates
00539     # - get the xy bounds
00540     # - transform to ijk
00541     # - clamp the bounds to the dimensions of the label image
00542     #
00543     bounds = self.brush.GetPoints().GetBounds()
00544     left = x + bounds[0]
00545     right = x + bounds[1]
00546     bottom = y + bounds[2]
00547     top = y + bounds[3]
00548 
00549     xyToIJK = labelLogic.GetXYToIJKTransform().GetMatrix()
00550     tlIJK = xyToIJK.MultiplyPoint( (left, top, 0, 1) )
00551     trIJK = xyToIJK.MultiplyPoint( (right, top, 0, 1) )
00552     blIJK = xyToIJK.MultiplyPoint( (left, bottom, 0, 1) )
00553     brIJK = xyToIJK.MultiplyPoint( (right, bottom, 0, 1) )
00554 
00555     dims = labelImage.GetDimensions()
00556 
00557     # clamp the top, bottom, left, right to the 
00558     # valid dimensions of the label image
00559     tl = [0,0,0]
00560     tr = [0,0,0]
00561     bl = [0,0,0]
00562     br = [0,0,0]
00563     for i in xrange(3):
00564       tl[i] = int(round(tlIJK[i]))
00565       if tl[i] < 0:
00566         tl[i] = 0
00567       if tl[i] >= dims[i]:
00568         tl[i] = dims[i] - 1
00569       tr[i] = int(round(trIJK[i]))
00570       if tr[i] < 0:
00571         tr[i] = 0
00572       if tr[i] > dims[i]:
00573         tr[i] = dims[i] - 1
00574       bl[i] = int(round(blIJK[i]))
00575       if bl[i] < 0:
00576         bl[i] = 0
00577       if bl[i] > dims[i]:
00578         bl[i] = dims[i] - 1
00579       br[i] = int(round(brIJK[i]))
00580       if br[i] < 0:
00581         br[i] = 0
00582       if br[i] > dims[i]:
00583         br[i] = dims[i] - 1
00584         
00585     #
00586     # get the layers and nodes 
00587     # and ijk to ras matrices including transforms
00588     #
00589     labelLogic = self.sliceLogic.GetLabelLayer()
00590     labelNode = labelLogic.GetVolumeNode()
00591     backgroundLogic = self.sliceLogic.GetLabelLayer()
00592     backgroundNode = backgroundLogic.GetVolumeNode()
00593     backgroundIJKToRAS = self.logic.getIJKToRASMatrix(backgroundNode)
00594     labelIJKToRAS = self.logic.getIJKToRASMatrix(labelNode)
00595 
00596 
00597     xyToRAS = sliceNode.GetXYToRAS()
00598     brushCenter = xyToRAS.MultiplyPoint( (x, y, 0, 1) )[:3]
00599     brushRadius = self.radius
00600 
00601     parameterNode = self.editUtil.getParameterNode()
00602     paintLabel = int(parameterNode.GetParameter("label"))
00603     paintOver = int(parameterNode.GetParameter("LabelEffect,paintOver"))
00604     paintThreshold = int(parameterNode.GetParameter("LabelEffect,paintThreshold"))
00605     paintThresholdMin = float(
00606         parameterNode.GetParameter("LabelEffect,paintThresholdMin"))
00607     paintThresholdMax = float(
00608         parameterNode.GetParameter("LabelEffect,paintThresholdMax"))
00609 
00610     #
00611     # set up the painter class and let 'r rip!
00612     #
00613     if not hasattr(self,"painter"):
00614       self.painter = slicer.vtkImageSlicePaint()
00615     self.painter.SetBackgroundImage(backgroundImage)
00616     self.painter.SetBackgroundIJKToWorld(backgroundIJKToRAS)
00617     self.painter.SetWorkingImage(labelImage)
00618     self.painter.SetWorkingIJKToWorld(labelIJKToRAS)
00619     self.painter.SetTopLeft( tl[0], tl[1], tl[2] )
00620     self.painter.SetTopRight( tr[0], tr[1], tr[2] )
00621     self.painter.SetBottomLeft( bl[0], bl[1], bl[2] )
00622     self.painter.SetBottomRight( br[0], br[1], br[2] )
00623     self.painter.SetBrushCenter( brushCenter[0], brushCenter[1], brushCenter[2] )
00624     self.painter.SetBrushRadius( brushRadius )
00625     self.painter.SetPaintLabel(paintLabel)
00626     self.painter.SetPaintOver(paintOver)
00627     self.painter.SetThresholdPaint(paintThreshold)
00628     self.painter.SetThresholdPaintRange(paintThresholdMin, paintThresholdMax)
00629     self.painter.Paint()
00630 
00631 
00632 #
00633 # PaintEffectLogic
00634 #
00635  
00636 class PaintEffectLogic(LabelEffect.LabelEffectLogic):
00637   """
00638   This class contains helper methods for a given effect
00639   type.  It can be instanced as needed by an PaintEffectTool
00640   or PaintEffectOptions instance in order to compute intermediate
00641   results (say, for user feedback) or to implement the final 
00642   segmentation editing operation.  This class is split
00643   from the PaintEffectTool so that the operations can be used
00644   by other code without the need for a view context.
00645   """
00646 
00647   def __init__(self,sliceLogic):
00648     super(PaintEffectLogic,self).__init__(sliceLogic)
00649 
00650 
00651 #
00652 # The PaintEffect class definition 
00653 #
00654 
00655 class PaintEffect(LabelEffect.LabelEffect):
00656   """Organizes the Options, Tool, and Logic classes into a single instance
00657   that can be managed by the EditBox
00658   """
00659 
00660   def __init__(self):
00661     # name is used to define the name of the icon image resource (e.g. PaintEffect.png)
00662     self.name = "PaintEffect"
00663     # tool tip is displayed on mouse hover
00664     self.toolTip = "Paint: circular paint brush for label map editing"
00665 
00666     self.options = PaintEffectOptions
00667     self.tool = PaintEffectTool
00668     self.logic = PaintEffectLogic
00669 
00670 """ Test:
00671 
00672 sw = slicer.app.layoutManager().sliceWidget('Red')
00673 import EditorLib
00674 pet = EditorLib.PaintEffectTool(sw)
00675 
00676 """
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Defines