Slicer3:Python:ScriptedModulesTipsNTricks

From Slicer Wiki
Jump to: navigation, search
Home < Slicer3:Python:ScriptedModulesTipsNTricks

Python Scripted Modules - Tips'N'Tricks

This page shows tips and tricks for writing Python Scripted Modules in Slicer3. Most examples are taken from the AtlasCreator module.

Adding your Logo to the Help&Acknowledgement Panel

It is possible to show your custom logo in the Help&Acknowledgement Panel of your Python Scripted Module.

1. Add your logo to the modules directory in .png format. 2. Edit your CMakeLists.txt and add the following section to copy the logo into Slicer's shared module directory. In this example, we include the UPenn logo:

# copy UPenn_logo.png
configure_file(
  ${CMAKE_CURRENT_SOURCE_DIR}/UPenn_logo.png
  ${CMAKE_BINARY_DIR}/${Slicer3_INSTALL_MODULES_SHARE_DIR}/${PROJECT_NAME}/UPenn_logo.png
  COPYONLY)
  
install(
  FILES ${CMAKE_CURRENT_SOURCE_DIR}/UPenn_logo.png
  DESTINATION ${Slicer3_INSTALL_MODULES_SHARE_DIR}/${PROJECT_NAME}
  )

3. In your ScriptedModuleGUI class, edit the BuildGUI function and add the following code after calling self.BuildHelpAndAboutFrame:

    def BuildGUI(self):
        '''
        Creates the Graphical User Interface (GUI) of the AtlasCreator. Gets called once during loading of the module.
        '''
...
        self._helpAboutFrame = self.BuildHelpAndAboutFrame(self._atlascreatorPage,helpText,aboutText)

        # include the UPenn logo
        logoFrame = self.GetLogoFrame()
        pathToLogo = os.path.normpath(slicer.Application.GetBinDir() + '/../share/Slicer3/Modules/AtlasCreator/UPenn_logo.png')
        logo = slicer.vtkKWIcon()
        
        logoReader = slicer.vtkPNGReader()
        logoReader.SetFileName(pathToLogo)
        logoReader.Update()
        
        logo.SetImage(logoReader.GetOutput())
        
        self._logoLabel = slicer.vtkKWLabel()
        self._logoLabel.SetParent(logoFrame)
        self._logoLabel.Create()
        self._logoLabel.SetImageToIcon(logo)
        slicer.TkCall("pack %s -side top -anchor nw -fill x -padx 2 -pady 2" % self._logoLabel.GetWidgetName())
...

The logo should appear after CMake gets executed the next time.

MRML Events

There are several example on how to observe GUI events in ScriptedModules. It is also possible to observe MRML events from the MRML Scene or from a node other than the vtkMRMLScriptedModuleNode.

The MRML observers can be safely added in the AddGUIObservers method.

Observe MRML Scene events

The following example shows how to observe the MRMLScene::NodeAddedEvent which is fired when a new MRML Node gets added to the scene. Also, we observe the MRMLScene::CloseEvent to update the GUI when the MRMLScene gets closed. Please remember to use the RemoveMRMLNodeObservers method to remove the MRML Scene observers (it is called automatically on teardown of the module).

# The event ids can be found in the MRML C++ header files (f.e. Libs/MRML/vtkMRMLScene.h..)
vtkMRMLScene_NodeAddedEvent = 66000
vtkMRMLScene_CloseEvent = 66003

class AtlasCreatorGUI(ScriptedModuleGUI):

    [...]

    '''=========================================================================================='''
    def AddGUIObservers(self):
        '''
        Add the Observers. This method gets called automatically when the module gets created.
        
        For convenience, we also add the MRML observers here.
        '''
        # listen to MRML scene events
        self._mrmlNodeAddedTag = self.AddMRMLObserverByNumber(slicer.MRMLScene,vtkMRMLScene_NodeAddedEvent)
        self._mrmlSceneCloseTag = self.AddMRMLObserverByNumber(slicer.MRMLScene,vtkMRMLScene_CloseEvent)

    [...]

    '''=========================================================================================='''    
    def RemoveMRMLNodeObservers(self):
        '''
        Remove MRML Node and MRML Scene Observers
        '''
        
        self.RemoveMRMLObserverByNumber(slicer.MRMLScene,vtkMRMLScene_NodeAddedEvent)
        self.RemoveMRMLObserverByNumber(slicer.MRMLScene,vtkMRMLScene_CloseEvent)



    '''=========================================================================================='''    
    def ProcessMRMLEvents(self,callerID,event,callDataID = None):
    ''' gets called, when an observed MRML event was fired '''
    
        # observe MRMLScene events
        if callerID == "MRMLScene" and event == vtkMRMLScene_NodeAddedEvent and callDataID:
            
            callDataAsMRMLNode = slicer.MRMLScene.GetNodeByID(callDataID)
            
            if isinstance(callDataAsMRMLNode, slicer.vtkMRMLScalarVolumeNode):
                print "A new vtkMRMLScalarVolumeNode was added: " + callDataID


        # observe MRMLScene Close events
        elif callerID == "MRMLScene" and event == vtkMRMLScene_CloseEvent:

            print "The MRMLScene was closed."
            self.UpdateGUI()                

    [...]


Observe MRML Node events

In this example, an existing vtkMRMLScalarVolumeNode is observed for changes in the associated ImageData which results in a vtkMRMLScalarVolumeNode::ImageDataModifiedEvent. It is required to know the ID of the MRML node to add observers. This can be coupled with listening to the MRMLScene events above (save the ID when a new MRMLNode of a certain type was added to the scene and then create observers). Here, we add the observer also in the AddGUIObservers method but in fact this can happen everywhere - even in ProcessMRMLEvents itself.

# The event ids can be found in the MRML C++ header files (f.e. Libs/MRML/vtkMRMLVolumeNode.h)
vtkMRMLVolumeNode_ImageDataModifiedEvent = 18001


class AtlasCreatorGUI(ScriptedModuleGUI):

    [...]

    def AddGUIObservers(self):
        # first, we listen to a MRML node event
        self._scalarVolumeNode = slicer.MRMLScene.GetNodeByID("vtkMRMLScalarVolumeNode1")
        self._imagedataModifiedTag = self.AddMRMLObserverByNumber(self._scalarVolumeNode,vtkMRMLVolumeNode_ImageDataModifiedEvent)

    [...]


    def ProcessMRMLEvents(self,callerID,event,callDataID = None):
    ''' gets called, when an observed MRML event was fired '''
    
        # observe MRMLNode events
        if callerID == "vtkMRMLScalarVolumeNode1" and event == vtkMRMLVolumeNode_ImageDataModifiedEvent:
            print "ImageData of vtkMRMLScalarVolumeNode1 was modified."

    [...]

Use a custom MRML Node for your Scripted Module

By default a Scripted Module uses the vtkMRMLScriptedModuleNode as a storage container. It is also possible to create your own node for this purpose. Since MRMLNodes have to derive from a vtkMRMLNode base class and this is not possible in Python, they should be written in C++.

A full example can be found in Modules/AtlasCreator/Cxx.

1. To include C++ code in your Python Scripted Module, it makes sense to create a sub-directory Cxx/ and create a gui-less module to include and register a custom MRML Node. Instructions for gui-less modules are available here.

2. The MRML Node itself can be written by using the default practices.

3. After creating the MRML Node, the RegisterNodes() method in the logic should be used to register the custom MRML Node within the MRML Scene. This is required for Loading and Saving the scene. This is the only real code the gui-less module has to include.

//----------------------------------------------------------------------------
void vtkAtlasCreatorCxxModuleLogic::RegisterNodes()
{

  vtkMRMLScene* scene = this->GetMRMLScene();

  if (scene)
    {
    vtkMRMLAtlasCreatorNode* atlasCreatorNode = vtkMRMLAtlasCreatorNode::New();
    scene->RegisterNodeClass(atlasCreatorNode);
    atlasCreatorNode->Delete();
    }

}

4. All the pieces can be glued together with CMake which also handles the Python interface through Tcl wrapping (using Subdirs(..) and vtk_wrap_tcl3 commands).

5. In the Scripted Module observe for MRMLScene::NodeAddedEvents as seen above and save pointers to your custom MRML Node IDs. Then, the UpdateMRML() and UpdateGUI() methods should use the pointers to update the node or the GUI.

Using KWWidget Callbacks

Writing Tests in Python