Slicer 4.2
Slicer is a multi-platform, free and open source software package for visualization and medical image computing
EditBox.py
Go to the documentation of this file.
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 
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Defines