|
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 qt 00003 from EditOptions import * 00004 import EditUtil 00005 import EditorLib 00006 00007 ######################################################### 00008 # 00009 # 00010 comment = """ 00011 00012 EditBox is a wrapper around a set of Qt widgets and other 00013 structures to manage the slicer4 edit box. 00014 00015 # TODO : 00016 """ 00017 # 00018 ######################################################### 00019 00020 # 00021 # The parent class definition 00022 # 00023 00024 class EditBox(object): 00025 00026 def __init__(self, parent=None, optionsFrame=None): 00027 self.effects = [] 00028 self.effectButtons = {} 00029 self.effectCursors = {} 00030 self.effectMapper = qt.QSignalMapper() 00031 self.effectMapper.connect('mapped(const QString&)', self.selectEffect) 00032 self.editUtil = EditUtil.EditUtil() 00033 self.undoRedo = EditUtil.UndoRedo() 00034 self.undoRedo.stateChangedCallback = self.updateUndoRedoButtons 00035 self.toggleShortcut = None 00036 00037 # check for extensions - if none have been registered, just create the empty dictionary 00038 try: 00039 slicer.modules.editorExtensions 00040 except AttributeError: 00041 slicer.modules.editorExtensions = {} 00042 00043 # register the builtin extensions 00044 self.editorBuiltins = {} 00045 self.editorBuiltins["PaintEffect"] = EditorLib.PaintEffect 00046 self.editorBuiltins["DrawEffect"] = EditorLib.DrawEffect 00047 self.editorBuiltins["ThresholdEffect"] = EditorLib.ThresholdEffect 00048 self.editorBuiltins["RectangleEffect"] = EditorLib.RectangleEffect 00049 self.editorBuiltins["LevelTracingEffect"] = EditorLib.LevelTracingEffect 00050 self.editorBuiltins["MakeModelEffect"] = EditorLib.MakeModelEffect 00051 self.editorBuiltins["ErodeEffect"] = EditorLib.ErodeEffect 00052 self.editorBuiltins["DilateEffect"] = EditorLib.DilateEffect 00053 self.editorBuiltins["ChangeLabelEffect"] = EditorLib.ChangeLabelEffect 00054 self.editorBuiltins["RemoveIslandsEffect"] = EditorLib.RemoveIslandsEffect 00055 self.editorBuiltins["IdentifyIslandsEffect"] = EditorLib.IdentifyIslandsEffect 00056 self.editorBuiltins["SaveIslandEffect"] = EditorLib.SaveIslandEffect 00057 self.editorBuiltins["ChangeIslandEffect"] = EditorLib.ChangeIslandEffect 00058 self.editorBuiltins["GrowCutEffect"] = EditorLib.GrowCutEffect 00059 self.editorBuiltins["FastMarchingEffect"] = EditorLib.FastMarchingEffect 00060 self.editorBuiltins["WandEffect"] = EditorLib.WandEffect 00061 00062 if not parent: 00063 self.parent = qt.QFrame() 00064 self.parent.setLayout( qt.QVBoxLayout() ) 00065 self.create() 00066 self.parent.show() 00067 else: 00068 self.parent = parent 00069 self.create() 00070 00071 # frame that holds widgets specific for each effect 00072 if not optionsFrame: 00073 self.optionsFrame = qt.QFrame(self.parent) 00074 else: 00075 self.optionsFrame = optionsFrame 00076 00077 # state variables for selected effect in the box 00078 # - currentOption is an instance of an option GUI 00079 # - currentTools is a list of EffectTool instances 00080 self.currentOption = None 00081 self.currentTools = [] 00082 00083 # listen for changes in the Interaction Mode 00084 appLogic = slicer.app.applicationLogic() 00085 interactionNode = appLogic.GetInteractionNode() 00086 self.interactionNodeTag = interactionNode.AddObserver(interactionNode.InteractionModeChangedEvent, self.onInteractionModeChanged) 00087 00088 def __del__(self): 00089 appLogic = slicer.app.applicationLogic() 00090 interactionNode = appLogic.GetInteractionNode() 00091 interactionNode.RemoveObserver(self.interactionNodeTag) 00092 00093 def onInteractionModeChanged(self, caller, event): 00094 if caller.IsA('vtkMRMLInteractionNode'): 00095 if caller.GetCurrentInteractionMode() != caller.ViewTransform: 00096 self.defaultEffect() 00097 00098 # 00099 # Public lists of the available effects provided by the editor 00100 # 00101 00102 # effects that change the mouse cursor 00103 availableMouseTools = ( 00104 "Paint", "Draw", "LevelTracing", "Rectangle", "ChangeIsland", "SaveIsland", "Wand", 00105 ) 00106 00107 # effects that operate from the menu (non mouse) 00108 availableOperations = ( 00109 "DefaultTool", "EraseLabel", 00110 "IdentifyIslands", "RemoveIslands", 00111 "ErodeLabel", "DilateLabel", "ChangeLabel", 00112 "MakeModel", "GrowCutSegment", 00113 "Threshold", 00114 "PreviousCheckPoint", "NextCheckPoint", 00115 ) 00116 00117 # allow overriding the developers name of the tool for a more user-friendly label name 00118 displayNames = {} 00119 displayNames["PreviousCheckPoint"] = "Undo" 00120 displayNames["NextCheckPoint"] = "Redo" 00121 00122 def findEffects(self, path=""): 00123 """fill the effects based built in and extension effects""" 00124 00125 # combined list of all effects 00126 self.mouseTools = EditBox.availableMouseTools 00127 self.effects = self.mouseTools + EditBox.availableOperations 00128 00129 # add builtins that have been registered 00130 self.effects = self.effects + tuple(self.editorBuiltins.keys()) 00131 00132 # add any extensions that have been registered 00133 self.effects = self.effects + tuple(slicer.modules.editorExtensions.keys()) 00134 00135 # for each effect 00136 # - look for implementation class of pattern *Effect 00137 # - get an icon name for the pushbutton 00138 iconDir = EditorLib.ICON_DIR 00139 00140 self.effectIconFiles = {} 00141 self.effectModes = {} 00142 self.icons = {} 00143 for effect in self.effects: 00144 self.effectIconFiles[effect] = iconDir + effect + '.png' 00145 00146 if effect in slicer.modules.editorExtensions.keys(): 00147 extensionEffect = slicer.modules.editorExtensions[effect]() 00148 module = eval('slicer.modules.%s' % effect.lower()) 00149 iconPath = os.path.join( os.path.dirname(module.path),"%s.png" % effect) 00150 self.effectIconFiles[effect] = iconPath 00151 00152 # special case for renamed effect 00153 self.effectIconFiles["Rectangle"] = iconDir + "ImplicitRectangle" + '.png' 00154 00155 # TOOD: add icons for builtins as resource or installed image directory 00156 self.effectIconFiles["PaintEffect"] = self.effectIconFiles["Paint"] 00157 self.effectIconFiles["DrawEffect"] = self.effectIconFiles["Draw"] 00158 self.effectIconFiles["ThresholdEffect"] = self.effectIconFiles["Threshold"] 00159 self.effectIconFiles["RectangleEffect"] = self.effectIconFiles["Rectangle"] 00160 self.effectIconFiles["LevelTracingEffect"] = self.effectIconFiles["LevelTracing"] 00161 self.effectIconFiles["MakeModelEffect"] = self.effectIconFiles["MakeModel"] 00162 self.effectIconFiles["ErodeEffect"] = self.effectIconFiles["ErodeLabel"] 00163 self.effectIconFiles["DilateEffect"] = self.effectIconFiles["DilateLabel"] 00164 self.effectIconFiles["IdentifyIslandsEffect"] = self.effectIconFiles["IdentifyIslands"] 00165 self.effectIconFiles["ChangeIslandEffect"] = self.effectIconFiles["ChangeIsland"] 00166 self.effectIconFiles["RemoveIslandsEffect"] = self.effectIconFiles["RemoveIslands"] 00167 self.effectIconFiles["SaveIslandEffect"] = self.effectIconFiles["SaveIsland"] 00168 self.effectIconFiles["ChangeIslandEffect"] = self.effectIconFiles["ChangeIsland"] 00169 self.effectIconFiles["ChangeLabelEffect"] = self.effectIconFiles["ChangeLabel"] 00170 self.effectIconFiles["GrowCutEffect"] = self.effectIconFiles["GrowCutSegment"] 00171 self.effectIconFiles["Wand"] = self.effectIconFiles["WandEffect"] 00172 00173 def createButtonRow(self, effects, rowLabel=""): 00174 """ create a row of the edit box given a list of 00175 effect names (items in _effects(list) """ 00176 00177 rowFrame = qt.QFrame(self.mainFrame) 00178 self.mainFrame.layout().addWidget(rowFrame) 00179 self.rowFrames.append(rowFrame) 00180 hbox = qt.QHBoxLayout() 00181 rowFrame.setLayout( hbox ) 00182 00183 if rowLabel: 00184 label = qt.QLabel(rowLabel) 00185 hbox.addWidget(label) 00186 00187 for effect in effects: 00188 # check that the effect belongs in our list of effects before including 00189 if (effect in self.effects): 00190 i = self.icons[effect] = qt.QIcon(self.effectIconFiles[effect]) 00191 a = self.actions[effect] = qt.QAction(i, '', rowFrame) 00192 self.effectButtons[effect] = b = self.buttons[effect] = qt.QToolButton() 00193 b.setDefaultAction(a) 00194 b.setToolTip(effect) 00195 if EditBox.displayNames.has_key(effect): 00196 b.setToolTip(EditBox.displayNames[effect]) 00197 hbox.addWidget(b) 00198 00199 # Setup the mapping between button and its associated effect name 00200 self.effectMapper.setMapping(self.buttons[effect], effect) 00201 # Connect button with signal mapper 00202 self.buttons[effect].connect('clicked()', self.effectMapper, 'map()') 00203 00204 hbox.addStretch(1) 00205 00206 # create the edit box 00207 def create(self): 00208 00209 self.findEffects() 00210 00211 self.mainFrame = qt.QFrame(self.parent) 00212 vbox = qt.QVBoxLayout() 00213 self.mainFrame.setLayout(vbox) 00214 self.parent.layout().addWidget(self.mainFrame) 00215 00216 # 00217 # the buttons 00218 # 00219 self.rowFrames = [] 00220 self.actions = {} 00221 self.buttons = {} 00222 self.icons = {} 00223 self.callbacks = {} 00224 00225 # create all of the buttons 00226 # createButtonRow() ensures that only effects in self.effects are exposed, 00227 self.createButtonRow( ("DefaultTool", "EraseLabel", "PaintEffect", "DrawEffect", "WandEffect", "LevelTracingEffect", "RectangleEffect", "IdentifyIslandsEffect", "ChangeIslandEffect", "RemoveIslandsEffect", "SaveIslandEffect") ) 00228 self.createButtonRow( ("ErodeEffect", "DilateEffect", "GrowCutEffect", "ThresholdEffect", "ChangeLabelEffect", "MakeModelEffect", "FastMarchingEffect") ) 00229 00230 extensions = [] 00231 for k in slicer.modules.editorExtensions: 00232 extensions.append(k) 00233 self.createButtonRow( extensions ) 00234 00235 self.createButtonRow( ("PreviousCheckPoint", "NextCheckPoint"), rowLabel="Undo/Redo: " ) 00236 00237 # 00238 # the labels 00239 # 00240 self.toolsActiveToolFrame = qt.QFrame(self.parent) 00241 self.toolsActiveToolFrame.setLayout(qt.QHBoxLayout()) 00242 self.parent.layout().addWidget(self.toolsActiveToolFrame) 00243 self.toolsActiveTool = qt.QLabel(self.toolsActiveToolFrame) 00244 self.toolsActiveTool.setText( 'Active Tool:' ) 00245 self.toolsActiveTool.setStyleSheet("background-color: rgb(232,230,235)") 00246 self.toolsActiveToolFrame.layout().addWidget(self.toolsActiveTool) 00247 self.toolsActiveToolName = qt.QLabel(self.toolsActiveToolFrame) 00248 self.toolsActiveToolName.setText( '' ) 00249 self.toolsActiveToolName.setStyleSheet("background-color: rgb(232,230,235)") 00250 self.toolsActiveToolFrame.layout().addWidget(self.toolsActiveToolName) 00251 00252 vbox.addStretch(1) 00253 00254 self.updateUndoRedoButtons() 00255 00256 def setActiveToolLabel(self,name): 00257 if EditBox.displayNames.has_key(name): 00258 name = EditBox.displayNames[name] 00259 self.toolsActiveToolName.setText(name) 00260 00261 # 00262 # switch to the default tool 00263 # 00264 def defaultEffect(self): 00265 self.selectEffect("DefaultTool") 00266 00267 # 00268 # manage the editor effects 00269 # 00270 def selectEffect(self, effectName): 00271 00272 if effectName == "EraseLabel": 00273 self.editUtil.toggleLabel() 00274 return 00275 elif effectName == "PreviousCheckPoint": 00276 self.undoRedo.undo() 00277 return 00278 elif effectName == "NextCheckPoint": 00279 self.undoRedo.redo() 00280 return 00281 00282 # 00283 # If there is no background volume or label map, do nothing 00284 # 00285 if not self.editUtil.getBackgroundVolume(): 00286 return 00287 if not self.editUtil.getLabelVolume(): 00288 return 00289 00290 # 00291 # an effect was selected, so build an options GUI 00292 # - check to see if it is an extension effect, 00293 # if not, try to create it, else ignore it 00294 # For extensions, look for 'effect'Options and 'effect'Tool 00295 # in the editorExtensions map and use those to create the 00296 # effect 00297 # 00298 if self.currentOption: 00299 # clean up any existing effect 00300 self.currentOption.__del__() 00301 self.currentOption = None 00302 for tool in self.currentTools: 00303 tool.sliceWidget.unsetCursor() 00304 tool.cleanup() 00305 self.currentTools = [] 00306 00307 # look at builtins and extensions 00308 # - TODO: other effect styles are deprecated 00309 effectClass = None 00310 if effectName in slicer.modules.editorExtensions.keys(): 00311 effectClass = slicer.modules.editorExtensions[effectName]() 00312 elif effectName in self.editorBuiltins.keys(): 00313 effectClass = self.editorBuiltins[effectName]() 00314 if effectClass: 00315 # for effects, create an options gui and an 00316 # instance for every slice view 00317 self.currentOption = effectClass.options(self.optionsFrame) 00318 self.currentOption.undoRedo = self.undoRedo 00319 self.currentOption.defaultEffect = self.defaultEffect 00320 self.currentOption.create() 00321 self.currentOption.updateGUI() 00322 layoutManager = slicer.app.layoutManager() 00323 sliceNodeCount = slicer.mrmlScene.GetNumberOfNodesByClass('vtkMRMLSliceNode') 00324 for nodeIndex in xrange(sliceNodeCount): 00325 # find the widget for each node in scene 00326 sliceNode = slicer.mrmlScene.GetNthNodeByClass(nodeIndex, 'vtkMRMLSliceNode') 00327 sliceWidget = layoutManager.sliceWidget(sliceNode.GetLayoutName()) 00328 if sliceWidget: 00329 tool = effectClass.tool(sliceWidget) 00330 tool.undoRedo = self.undoRedo 00331 self.currentTools.append(tool) 00332 self.currentOption.tools = self.currentTools 00333 else: 00334 # fallback to internal classes 00335 try: 00336 options = eval("%sOptions" % effectName) 00337 self.currentOption = options(self.optionsFrame) 00338 except NameError, AttributeError: 00339 # No options for this effect, skip it 00340 pass 00341 00342 self.setActiveToolLabel(effectName) 00343 00344 # mouse tool changes cursor, and dismisses popup/menu 00345 toolName = effectName 00346 if toolName.endswith('Effect'): 00347 toolName = effectName[:-len('Effect')] 00348 00349 if toolName in self.mouseTools: 00350 # set the interaction mode in case there was an active place going on 00351 appLogic = slicer.app.applicationLogic() 00352 interactionNode = appLogic.GetInteractionNode() 00353 interactionNode.SetCurrentInteractionMode(interactionNode.ViewTransform) 00354 # make an appropriate cursor for the tool 00355 cursor = self.cursorForEffect(effectName) 00356 for tool in self.currentTools: 00357 tool.sliceWidget.setCursor(cursor) 00358 00359 def cursorForEffect(self,effectName): 00360 """Return an instance of QCursor customized for the given effectName. 00361 TODO: this could be moved to the EffectTool class so that effects can manage 00362 per-widget cursors, possibly turning them off or making them dynamic 00363 """ 00364 if not effectName in self.effectCursors: 00365 baseImage = qt.QImage(":/Icons/AnnotationPointWithArrow.png") 00366 effectImage = qt.QImage(self.effectIconFiles[effectName]) 00367 width = max(baseImage.width(), effectImage.width()) 00368 pad = -9 00369 height = pad + baseImage.height() + effectImage.height() 00370 width = height = max(width,height) 00371 center = int(width/2) 00372 cursorImage = qt.QImage(width, height, qt.QImage().Format_ARGB32) 00373 painter = qt.QPainter() 00374 cursorImage.fill(0) 00375 painter.begin(cursorImage) 00376 point = qt.QPoint(center - (baseImage.width()/2), 0) 00377 painter.drawImage(point, baseImage) 00378 point.setX(center - (effectImage.width()/2)) 00379 point.setY(cursorImage.height() - effectImage.height()) 00380 painter.drawImage(point, effectImage) 00381 painter.end() 00382 cursorPixmap = qt.QPixmap() 00383 cursorPixmap = cursorPixmap.fromImage(cursorImage) 00384 self.effectCursors[effectName] = qt.QCursor(cursorPixmap,center,0) 00385 return self.effectCursors[effectName] 00386 00387 def updateUndoRedoButtons(self): 00388 self.effectButtons["PreviousCheckPoint"].enabled = self.undoRedo.undoEnabled() 00389 self.effectButtons["NextCheckPoint"].enabled = self.undoRedo.redoEnabled() 00390 00391 def isFloatingMode(self): 00392 return self.mainFrame.parent() is None 00393 00394 def enterFloatingMode(self): 00395 self.mainFrame.setParent(None) 00396 cursorPosition = qt.QCursor().pos() 00397 w = self.mainFrame.width 00398 h = self.mainFrame.height 00399 self.mainFrame.pos = qt.QPoint(cursorPosition.x() - w/2, cursorPosition.y() - h/2) 00400 self.mainFrame.show() 00401 self.mainFrame.raise_() 00402 Key_Space = 0x20 # not in PythonQt 00403 self.toggleShortcut = qt.QShortcut(self.mainFrame) 00404 self.toggleShortcut.setKey( qt.QKeySequence(Key_Space) ) 00405 self.toggleShortcut.connect( 'activated()', self.toggleFloatingMode ) 00406 00407 def cancelFloatingMode(self): 00408 if self.isFloatingMode(): 00409 if self.toggleShortcut: 00410 self.toggleShortcut.disconnect('activated()') 00411 self.toggleShortcut.setParent(None) 00412 self.toggleShortcut = None 00413 self.mainFrame.setParent(self.parent) 00414 self.parent.layout().addWidget(self.mainFrame) 00415 00416 def toggleFloatingMode(self): 00417 """Set or clear the parent of the edit box so that it is a top level 00418 window or embeded in the gui as appropriate. Meant to be associated 00419 with the space bar shortcut for the mainWindow, set in Editor.py""" 00420 if self.isFloatingMode(): 00421 self.cancelFloatingMode() 00422 else: 00423 self.enterFloatingMode()
1.7.4