|
Slicer 4.2
Slicer is a multi-platform, free and open source software package for visualization and medical image computing
|
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 """
1.7.4