|
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 tcl 00003 from __main__ import qt 00004 from EditOptions import * 00005 import EditUtil 00006 00007 00008 ######################################################### 00009 # 00010 # 00011 comment = """ 00012 00013 EditBox is a wrapper around a set of Qt widgets and other 00014 structures to manage the slicer4 edit box. 00015 00016 # TODO : 00017 """ 00018 # 00019 ######################################################### 00020 00021 TODO = """ 00022 # set the state of an icon in all edit boxes (e.g. undo/redo) 00023 proc SetButtonState {effect state} { 00024 foreach editBox [itcl::find objects -class EditBox] { 00025 $editBox setButtonState $effect $state 00026 } 00027 } 00028 """ 00029 00030 00031 # 00032 # The parent class definition 00033 # 00034 00035 class EditBox(object): 00036 00037 def __init__(self, parent=0, optionsFrame=None, embedded=False, suppliedEffects=[]): 00038 self.effects = [] 00039 self.effectButtons = {} 00040 self.effectMapper = qt.QSignalMapper() 00041 self.effectMapper.connect('mapped(const QString&)', self.selectEffect) 00042 self.editUtil = EditUtil.EditUtil() 00043 00044 # check for extensions - if none have been registered, just create the empty dictionary 00045 try: 00046 slicer.modules.editorExtensions 00047 except AttributeError: 00048 slicer.modules.editorExtensions = {} 00049 00050 # embedded boolean specifies whether or not this edit box is to be embedded 00051 # into another moduleWidget 00052 # - if it is, all effect buttons will be displayed in a single row 00053 self.embedded = embedded 00054 00055 # save the list of supplied effects that the caller wants to use 00056 # (should be a subset of EditBox.availableMouseTools + EditBox.availableOperations) 00057 self.suppliedEffects = suppliedEffects 00058 00059 if parent == 0: 00060 self.parent = qt.QFrame() 00061 self.parent.setLayout( qt.QVBoxLayout() ) 00062 self.create() 00063 self.parent.show() 00064 else: 00065 self.parent = parent 00066 self.create() 00067 00068 # frame that holds widgets specific for each effect 00069 if not optionsFrame: 00070 self.optionsFrame = qt.QFrame(self.parent) 00071 else: 00072 self.optionsFrame = optionsFrame 00073 self.currentOption = None 00074 00075 # 00076 # Public lists of the available effects provided by the editor 00077 # 00078 00079 # effects that change the mouse cursor 00080 availableMouseTools = ( 00081 "ChangeIsland", "ChooseColor", 00082 "ImplicitCube", "ImplicitEllipse", "ImplicitRectangle", 00083 "Draw", "RemoveIslands", "ConnectedComponents", 00084 "ThresholdBucket", "ThresholdPaintLabel", "SaveIsland", "SlurpColor", "Paint", 00085 "DefaultTool", "LevelTracing", "MakeModel", "Wand", "GrowCutSegment", 00086 ) 00087 00088 # effects that operate from the menu 00089 availableOperations = ( 00090 "ErodeLabel", "DilateLabel", "DeleteFiducials", "LabelOpacity", 00091 "ChangeLabel", "FiducialVisibilityOff", 00092 "FiducialVisibilityOn", "GoToEditorModule", 00093 "IdentifyIslands", 00094 "LabelVisibilityOff", "LabelVisibilityOn", "NextFiducial", 00095 "SnapToGridOff", "SnapToGridOn", 00096 "EraseLabel", "Threshold", "PinOpen", "PreviousFiducial", "InterpolateLabels", "LabelOpacity", 00097 "ToggleLabelOutline", "Watershed", "PreviousCheckPoint", "NextCheckPoint", 00098 ) 00099 00100 # these buttons do not switch you out of the current tool 00101 availableNonmodal = ( 00102 "FiducialVisibilityOn", "LabelVisibilityOff", "LabelVisibilityOn", 00103 "NextFiducial", "PreviousFiducial", "DeleteFiducials", "SnapToGridOn", "SnapToGridOff", 00104 "EraseLabel", "PreviousCheckPoint", "NextCheckPoint", "ToggleLabelOutline", 00105 "SnapToGridOff", "SnapToGridOn", "LabelOpacity" 00106 ) 00107 00108 # these buttons start disabled (check points will re-enable when circumstances are right) 00109 availableDisabled = ( 00110 "ChooseColor", 00111 "ImplicitCube", "ImplicitEllipse", 00112 "ConnectedComponents", 00113 "SlurpColor", 00114 "ThresholdPaintLabel", "ThresholdBucket", 00115 "DeleteFiducials", "LabelOpacity", 00116 "FiducialVisibilityOff", 00117 "FiducialVisibilityOn", 00118 "LabelVisibilityOff", "LabelVisibilityOn", 00119 "SnapToGridOff", "SnapToGridOn", 00120 "InterpolateLabels", "LabelOpacity", 00121 "ToggleLabelOutline", "Watershed", "Wand", 00122 ) 00123 00124 # allow overriding the developers name of the tool for a more user-friendly label name 00125 displayNames = {} 00126 displayNames["PreviousCheckPoint"] = "Undo" 00127 displayNames["NextCheckPoint"] = "Redo" 00128 00129 # calculates the intersection of two flat lists 00130 @classmethod 00131 def listIntersection(cls, inList1, inList2): 00132 outList = [val for val in inList1 if val in inList2] 00133 return outList 00134 00135 # fill the _effects array bases on what you find in the interpreter 00136 # if a list of effects was supplied, then use that list instead of all of the effects 00137 def findEffects(self, path=""): 00138 00139 # for now, the built in effects are hard-coded to facilitate 00140 # the icons and layout 00141 00142 self.effects = [] 00143 00144 # if a list of effects was supplied, then use that list instead of all of the effects 00145 # don't forget to check that the supplied effects are valid: ensure they exist in the lists of available effects 00146 00147 if (self.suppliedEffects): 00148 self.mouseTools = tuple(self.listIntersection(self.suppliedEffects, EditBox.availableMouseTools)) 00149 self.operations = tuple(self.listIntersection(self.suppliedEffects, EditBox.availableOperations)) 00150 self.nonmodal = tuple(self.listIntersection(self.suppliedEffects, EditBox.availableNonmodal)) 00151 self.disabled = tuple(self.listIntersection(self.suppliedEffects, EditBox.availableDisabled)) 00152 # if a list of effects is not supplied, then provide all effects 00153 else: 00154 self.mouseTools = EditBox.availableMouseTools 00155 self.operations = EditBox.availableOperations 00156 self.nonmodal = EditBox.availableNonmodal 00157 self.disabled = EditBox.availableDisabled 00158 00159 ''' 00160 for key in slicer.modules.editorExtensions.keys(): 00161 e = slicer.modules.editorExtensions[key]() 00162 if 'MouseTool' in e.attributes: 00163 self.mouseTools.append(key) 00164 if 'Nonmodal' in e.attributes: 00165 self.operations.append(key) 00166 if 'Disabled' in e.attributes: 00167 self.disabled.append(key) 00168 ''' 00169 00170 00171 # combined list of all effects 00172 self.effects = self.mouseTools + self.operations 00173 00174 # add any extensions that have been registered 00175 self.effects = self.effects + tuple(slicer.modules.editorExtensions.keys()) 00176 00177 # for each effect 00178 # - look for implementation class of pattern *Effect 00179 # - get an icon name for the pushbutton 00180 iconDir = os.environ['SLICER_HOME'] + '/lib/Slicer3/SlicerBaseGUI/Tcl/ImageData/' 00181 00182 self.effectClasses = {} 00183 self.effectIconFiles = {} 00184 self.effectModes = {} 00185 self.icons = {} 00186 for effect in self.effects: 00187 tclclass = tcl('info command %sEffect' % effect) 00188 if tclclass != '': 00189 self.effectClasses[effect] = tclclass 00190 else: 00191 self.effectClasses[effect] = "EffectSWidget" 00192 00193 for iconType in ( "", "Selected", "Disabled" ): 00194 self.effectIconFiles[effect,iconType] = iconDir + effect + iconType + '.png' 00195 iconMode = "" 00196 if self.disabled.__contains__(effect): 00197 # - don't use the disabled icon for now - Qt's setEnabled method works fine 00198 #iconMode = "Disabled" 00199 pass 00200 00201 self.effectModes[effect] = iconMode 00202 00203 00204 # 00205 # create a row of the edit box given a list of 00206 # effect names (items in _effects(list) 00207 # 00208 def createButtonRow(self, effects): 00209 00210 f = qt.QFrame(self.parent) 00211 self.parent.layout().addWidget(f) 00212 self.rowFrames.append(f) 00213 hbox = qt.QHBoxLayout() 00214 f.setLayout( hbox ) 00215 00216 hbox.addStretch(1) 00217 for effect in effects: 00218 # check that the effect belongs in our list of effects before including 00219 # (handles non-embedded widgets where the caller has supplied a custom list of effects) 00220 if (effect in self.effects): 00221 i = self.icons[effect] = qt.QIcon(self.effectIconFiles[effect,self.effectModes[effect]]) 00222 a = self.actions[effect] = qt.QAction(i, '', f) 00223 self.effectButtons[effect] = b = self.buttons[effect] = qt.QToolButton() 00224 b.setDefaultAction(a) 00225 b.setToolTip(effect) 00226 if EditBox.displayNames.has_key(effect): 00227 b.setToolTip(EditBox.displayNames[effect]) 00228 hbox.addWidget(b) 00229 if self.disabled.__contains__(effect): 00230 b.setDisabled(1) 00231 00232 # Setup the mapping between button and its associated effect name 00233 self.effectMapper.setMapping(self.buttons[effect], effect) 00234 # Connect button with signal mapper 00235 self.buttons[effect].connect('clicked()', self.effectMapper, 'map()') 00236 00237 hbox.addStretch(1) 00238 00239 # create the edit box 00240 def create(self): 00241 00242 self.findEffects() 00243 00244 # 00245 # the labels (not shown in embedded format) 00246 # 00247 self.toolsActiveToolFrame = qt.QFrame(self.parent) 00248 self.toolsActiveToolFrame.setLayout(qt.QHBoxLayout()) 00249 self.parent.layout().addWidget(self.toolsActiveToolFrame) 00250 self.toolsActiveTool = qt.QLabel(self.toolsActiveToolFrame) 00251 self.toolsActiveTool.setText( 'Active Tool:' ) 00252 self.toolsActiveTool.setStyleSheet("background-color: rgb(232,230,235)") 00253 self.toolsActiveToolFrame.layout().addWidget(self.toolsActiveTool) 00254 self.toolsActiveToolName = qt.QLabel(self.toolsActiveToolFrame) 00255 self.toolsActiveToolName.setText( '' ) 00256 self.toolsActiveToolName.setStyleSheet("background-color: rgb(232,230,235)") 00257 self.toolsActiveToolFrame.layout().addWidget(self.toolsActiveToolName) 00258 00259 # 00260 # the buttons 00261 # 00262 self.rowFrames = [] 00263 self.actions = {} 00264 self.buttons = {} 00265 self.icons = {} 00266 self.callbacks = {} 00267 00268 # if not using embedded format: create all of the buttons 00269 # createButtonRow() ensures that only effects in self.effects are exposed, 00270 # so if the user supplied a list of effects only those in that list will be exposed 00271 if (not self.embedded): 00272 self.createButtonRow( ("DefaultTool", "EraseLabel") ) 00273 self.createButtonRow( ("Paint", "Draw", "LevelTracing", "ImplicitRectangle") ) 00274 self.createButtonRow( ("IdentifyIslands", "ChangeIsland", "RemoveIslands", "SaveIsland") ) 00275 self.createButtonRow( ("ErodeLabel", "DilateLabel", "Threshold", "ChangeLabel") ) 00276 extensions = [] 00277 for k in slicer.modules.editorExtensions: 00278 extensions.append(k) 00279 self.createButtonRow( extensions ) 00280 self.createButtonRow( ("MakeModel", "GrowCutSegment") ) 00281 # TODO: add back prev/next fiducial 00282 #self.createButtonRow( ("PreviousFiducial", "NextFiducial") ) 00283 self.createButtonRow( ("PreviousCheckPoint", "NextCheckPoint") ) 00284 # if using embedded format: create all of the buttons in the effects list in a single row 00285 else: 00286 self.createButtonRow(self.effects) 00287 00288 self.updateCheckPointButtons() 00289 00290 def setActiveToolLabel(self,name): 00291 if EditBox.displayNames.has_key(name): 00292 name = EditBox.displayNames[name] 00293 self.toolsActiveToolName.setText(name) 00294 00295 # needs to be a valid effect name and state of "", Disabled, or Selected 00296 TODO = """ 00297 itcl::body EditBox::setButtonState {effect state} { 00298 $::slicer3::ApplicationGUI SetIconImage \ 00299 $_effects($effect,icon) $_effects($effect,imageData$state) 00300 $o($effect,button) SetImageToIcon $_effects($effect,icon) 00301 switch $state { 00302 Selected - 00303 "" { 00304 $o($effect,button) SetState 1 00305 } 00306 "Disabled" { 00307 $o($effect,button) SetState 0 00308 } 00309 } 00310 } 00311 """ 00312 00313 # 00314 # Pause running the current effect, reverting to the default tool 00315 # 00316 def pauseEffect(self): 00317 self.selectEffect("DefaultTool") 00318 00319 # 00320 # Resume running the effect that was being used before a pause (TODO) 00321 # 00322 def resumeEffect(self): 00323 pass 00324 00325 # 00326 # manage the editor effects 00327 # 00328 def selectEffect(self, effect): 00329 from slicer import app 00330 00331 # 00332 # if an effect was added, build an options GUI 00333 # - check to see if it is an extension effect, 00334 # if not, try to create it, else ignore it 00335 # 00336 if self.currentOption: 00337 self.currentOption.__del__() 00338 self.currentOption = None 00339 if effect in slicer.modules.editorExtensions.keys(): 00340 self.currentOption = slicer.modules.editorExtensions[effect](self.optionsFrame) 00341 else: 00342 try: 00343 options = eval("%sOptions" % effect) 00344 self.currentOption = options(self.optionsFrame) 00345 except NameError, AttributeError: 00346 print ("No options for %s." % effect) 00347 pass 00348 00349 # 00350 # If there is no background volume or label map, do nothing 00351 # 00352 # TODO should do this regardless of whether or not there is an option 00353 if not self.editUtil.getBackgroundVolume(): 00354 return 00355 if not self.editUtil.getLabelVolume(): 00356 return 00357 00358 app.restoreOverrideCursor() 00359 self.setActiveToolLabel(effect) 00360 if not self.nonmodal.__contains__(effect): 00361 tcl('EffectSWidget::RemoveAll') 00362 tcl('EditorSetActiveToolLabel %s' % effect) 00363 00364 # mouse tool changes cursor, and dismisses popup/menu 00365 mouseTool = False 00366 if self.mouseTools.__contains__(effect): 00367 mouseTool = True 00368 00369 if effect == "DefaultTool": 00370 # do nothing - this will reset cursor mode 00371 tcl('EditorSetActiveToolLabel DefaultTool') 00372 elif effect == "GoToEditorModule": 00373 tcl('EditorSelectModule') 00374 tcl('EditorSetActiveToolLabel DefaultTool') 00375 elif effect == "LabelCheckPoint": 00376 # save a copy of the current label layer into the scene 00377 tcl('EditorLabelCheckPoint') 00378 tcl('EditorSetActiveToolLabel DefaultTool') 00379 elif effect == "PreviousFiducial": 00380 tcl('::FiducialsSWidget::JumpAllToNextFiducial -1') 00381 tcl('EditorSetActiveToolLabel DefaultTool') 00382 elif effect == "NextFiducial": 00383 tcl('::FiducialsSWidget::JumpAllToNextFiducial 1') 00384 tcl('EditorSetActiveToolLabel DefaultTool') 00385 elif effect == "EraseLabel": 00386 tcl('EditorToggleErasePaintLabel') 00387 elif effect == "PreviousCheckPoint": 00388 tcl('EditorPerformPreviousCheckPoint') 00389 elif effect == "NextCheckPoint": 00390 tcl('EditorPerformNextCheckPoint') 00391 else: 00392 if effect == "GrowCutSegment": 00393 self.editorGestureCheckPoint() 00394 #volumesLogic = slicer.modules.volumes.logic() 00395 #print ("VolumesLogic is %s " % volumesLogic) 00396 #tcl('EditorGestureCheckPoint $%s' % volumesLogic) 00397 if mouseTool: 00398 # TODO: make some nice custom cursor shapes 00399 # - for now use the built in override cursor 00400 #pix = qt.QPixmap() 00401 #pix.load(self.effectIconFiles[effect,""]) 00402 #cursor = qt.QCursor(pix) 00403 #app.setOverrideCursor(cursor, 0, 0) 00404 cursor = qt.QCursor(1) 00405 app.setOverrideCursor(cursor) 00406 else: 00407 app.restoreOverrideCursor() 00408 00409 # 00410 # create an instance of the effect for each of the active sliceGUIs 00411 # - have the effect reset the tool label when completed 00412 # 00413 00414 ret = tcl('catch "EffectSWidget::Add %s" res' % self.effectClasses[effect]) 00415 if ret != '0': 00416 dialog = qt.QErrorMessage(self.parent) 00417 dialog.showMessage("Could not select effect.\n\nError was:\n%s" % tcl('set res')) 00418 else: 00419 tcl('EffectSWidget::ConfigureAll %s -exitCommand "EditorSetActiveToolLabel DefaultTool"' % self.effectClasses[effect]) 00420 00421 def updateCheckPointButtons(self): 00422 previousImagesExist = nextImagesExist = False 00423 if bool(int(tcl('info exists ::Editor(previousCheckPointImages)'))): 00424 previousImagesExist = bool(int(tcl('llength $::Editor(previousCheckPointImages)'))) 00425 if bool(int(tcl('info exists ::Editor(nextCheckPointImages)'))): 00426 nextImagesExist = bool(int(tcl('llength $::Editor(nextCheckPointImages)'))) 00427 if not self.embedded: 00428 self.effectButtons["PreviousCheckPoint"].setDisabled( not previousImagesExist ) 00429 self.effectButtons["NextCheckPoint"].setDisabled( not nextImagesExist ) 00430 00431 def editorGestureCheckPoint(self): 00432 labelID = self.editUtil.getLabelID() 00433 labelNode = slicer.mrmlScene.GetNodeByID(labelID) 00434 labelImage = labelNode.GetImageData() 00435 00436 backgroundID = self.editUtil.getBackgroundID() 00437 backgroundNode = slicer.mrmlScene.GetNodeByID(backgroundID) 00438 backgroundImage = backgroundNode.GetImageData() 00439 00440 labelDim = labelImage.GetDimensions() 00441 backgroundDim = backgroundImage.GetDimensions() 00442 00443 gestureID = None 00444 print ("printting label dimensions ") 00445 print labelDim 00446 print ("printing background dimensions ") 00447 print backgroundDim 00448 # if labelDim[0] != backgroundDim[0] | labelDim[1] != backgroundDim[1] | labelDim[2] != backgroundDim[2]: 00449 if labelDim != backgroundDim: 00450 dialog = qt.QErrorMessage(self.parent) 00451 dialog.showMessage("Label Image and Background Image Dimensions don't match. Select another label image. All previous gestures will be lost.") 00452 else: 00453 # flip label name and gesture name 00454 labelName = labelNode.GetName() 00455 gestureName = labelName 00456 labelName = labelName + '-growcut-input' 00457 00458 slicer.mrmlScene.GetNodeByID(labelID).SetName(gestureName) 00459 00460 nodes = slicer.mrmlScene.GetNodesByName(labelName) 00461 if nodes.GetNumberOfItems() == 0: 00462 volumesLogic = slicer.modules.volumes.logic() 00463 gestureNode = volumesLogic.CreateLabelVolume( slicer.mrmlScene, labelNode, labelName) 00464 gestureID = gestureNode.GetID() 00465 node = self.editorGetGestureParameterNode(gestureID) 00466 else: 00467 nNodes = nodes.GetNumberOfItems() 00468 foundNode = None 00469 for n in xrange(nNodes): 00470 vol = nodes.GetItemAsObject(n) 00471 volID = vol.GetID() 00472 volname = vol.GetName() 00473 if volname == gestureName: 00474 foundNode = volID 00475 gestureID = volID 00476 break 00477 if foundNode == None: 00478 volumesLogic = slicer.modules.volumes.logic() 00479 gestureNode = volumesLogic.CreateLabelVolume( slicer.mrmlScene, labelNode, gestureName) 00480 gestureID = gestureNode.GetID() 00481 node = self.editorGetGestureParameterNode(gestureID) 00482 else: 00483 node = self.editorGetGestureParameterNode(foundNode) 00484 numNodes = slicer.mrmlScene.GetNumberOfNodesByClass('vtkMRMLSliceCompositeNode') 00485 for n in xrange(numNodes): 00486 cnode = slicer.mrmlScene.GetNthNodeByClass(n, 'vtkMRMLSliceCompositeNode') 00487 cnode.SetReferenceLabelVolumeID(labelNode.GetID()) 00488 cnode.SetLabelOpacity(0.6) 00489 cnode.SetReferenceForegroundVolumeID(gestureID) 00490 cnode.SetForegroundOpacity(0.4) 00491 00492 def editorGetGestureParameterNode(self, id): 00493 node = None 00494 nNodes = slicer.mrmlScene.GetNumberOfNodesByClass('vtkMRMLScriptedModuleNode') 00495 for n in xrange(nNodes): 00496 compNode = slicer.mrmlScene.GetNthNodeByClass(n, 'vtkMRMLScriptedModuleNode') 00497 nodeid = None 00498 if compNode.GetModuleName() == 'Editor': 00499 nodeId = compNode.GetParameter('gestureid') 00500 if nodeId: 00501 val = compNode.SetParameter('gestureid', nodeId) 00502 node = val 00503 break 00504 if node == None: 00505 node = tcl('EditorCreateGestureParameterNode %s' % id) 00506 return node 00507 00508
1.7.4