Documentation/Nightly/Developers/Python scripting

From SlicerWiki
Jump to: navigation, search
Home < Documentation < Nightly < Developers < Python scripting


Contents

Start Here

Please read these slides and work through the example code.

Refer to this description that includes links to all the documentation.


Background

This is an evolution of the python implementation in slicer3. Slicer's APIs are now natively wrapped in python.

Topics like plotting are still experimental in slicer4.

See this 2012 presentation on the state of python in slicer4.

See the python slicer4 tutorial for more examples.

Slicer Self Tests can be written in python, and provide a good source of examples for manipulating the data, logic, and gui of slicer.

Start Here for Scripted Module and Extension Development

An extensive tutorial and reference page was created for the Slicer/Python breakout session at the NA-MIC 2013 Summer Project Week.

Usage options

Python Interactor

Use the Window->Python Interactor (Control-3 on window/linux, Command-3 on mac) to bring up the Qt-based console with access to the vtk, Qt, and Slicer wrapped APIs.

Most python code can be installed and run from this window, but because it exists in the event driven Qt GUI environment, some operations like, like parallel processing or headless operation, are not easily supported.

Examples

Start slicer4 and bring up python console. Load a volume like this:

>>> slicer.util.loadVolume(slicer.app.slicerHome + "/share/MRML/Testing/TestData/fixed.nrrd")

Get the volume node for that volume:

>>> n = getNode('fixed')

You can use Tab to see lists of methods for a class instance.

Accessing Volume data as numpy array

You can easily inspect and manipulate volume data using numpy and related code. In slicer you can do this:

>>> a = array('fixed')

and a will be a pointer to the appropriate data (no data copying). Scalar volumes become three-dimensional arrays, while vector volumes become 4D, and tensor volumes are 5D. All arrays can be manipulated directly.

If you don't process the data in-place but you have computation results in a numpy array, then you have to copy the contents of a numpy array into a volume:

>>> resultVolumeNode = getNode('fixed')
>>> resultVtkArray = vtk.util.numpy_support.numpy_to_vtk(a, deep=True, array_type=vtk.VTK_SHORT)
>>> resultVolumeNode.GetImageData().SetScalars(resultVtkArray)

Running a CLI from Python

Here's an example to create a model from a volume using the Grayscale Model Maker

def grayModel(volumeNode):
  parameters = {}
  parameters["InputVolume"] = volumeNode.GetID()
  outModel = slicer.vtkMRMLModelNode()
  slicer.mrmlScene.AddNode( outModel )
  parameters["OutputGeometry"] = outModel.GetID()
  grayMaker = slicer.modules.grayscalemodelmaker
  return (slicer.cli.runSync(grayMaker, None, parameters))

To try this, download the MRHead dataset from the Sample Data and paste the code into the python console and then run this:

v = getNode('MRHead')
cliNode = grayModel(v)

Here is an example for running a CLI module from a scripted module: https://github.com/fedorov/ChangeTrackerPy/blob/master/ChangeTracker/ChangeTrackerWizard/ChangeTrackerRegistrationStep.py#L56-L67

Passing Fiducials to CLIs via a Python Script


import SampleData
sampleDataLogic = SampleData.SampleDataLogic()
head = sampleDataLogic.downloadMRHead()
volumesLogic = slicer.modules.volumes.logic()
headLabel = volumesLogic.CreateLabelVolume(slicer.mrmlScene, head, 'head-label')

fiducialNode = slicer.vtkMRMLAnnotationFiducialNode()
fiducialNode.SetFiducialWorldCoordinates((1,0,5))
fiducialNode.SetName('Seed Point')
fiducialNode.Initialize(slicer.mrmlScene)
fiducialsList = getNode('Fiducials List')

params = {'inputVolume': head.GetID(), 'outputVolume': headLabel.GetID(), 'seed' : fiducialsList.GetID(), 'iterations' : 2} 

cliNode = slicer.cli.runSync(slicer.modules.simpleregiongrowingsegmentation, None, params)

Running CLI in the background

If the CLI module is executed using slicer.cli.run method then the CLI module runs in a background thread, so the call to grayModel will return right away and the user interface will not be blocked. The slicer.cli.run call returns a cliNode (an instance of vtkMRMLCommandLineModuleNode) which can be used to monitor the progress of the module.

In this example we create a simple callback that will be called whenever the cliNode is modified. The status will tell you if the nodes is Pending, Running, or Completed.

def printStatus(caller, event):
  print("Got a %s from a %s" % (event, caller.GetClassName()))
  if caller.IsA('vtkMRMLCommandLineModuleNode'):
    print("Status is %s" % caller.GetStatusString())

cliNode.AddObserver('ModifiedEvent', printStatus)

Get list of parameter names

The following script prints all the parameter names of a CLI parameter node:

cliModule = slicer.modules.grayscalemodelmaker
n=cliModule.cliModuleLogic().CreateNode()
for groupIndex in xrange(0,n.GetNumberOfParameterGroups()):
  for parameterIndex in xrange(0,n.GetNumberOfParametersInGroup(groupIndex)):
    print '  Parameter ({0}/{1}): {2} ({3})'.format(groupIndex, parameterIndex, n.GetParameterName(groupIndex, parameterIndex), n.GetParameterLabel(groupIndex, parameterIndex))

Accessing slice vtkRenderWindows from slice views

The example below shows how to get the rendered slice window.

lm = slicer.app.layoutManager()
redWidget = lm.sliceWidget('Red')
redView = redWidget.sliceView()
wti = vtk.vtkWindowToImageFilter()
wti.SetInput(redView.renderWindow())
wti.Update()
v = vtk.vtkImageViewer()
v.SetColorWindow(255)
v.SetColorLevel(128)
v.SetInputConnection(wti.GetOutputPort())
v.Render()

Script Repository

See ScriptRepository


Developer FAQ: Python Scripting

How to access a scripted module from python scripts

All slicer modules are accessible in the slicer.modules namespace. For example, sampledata module can be accessed as slicer.modules.sampledata.

To access a module's widget, use widgetRepresentation() method to get the C++ base class and its self() method to get the Python class. For example, slicer.modules.sampledata.widgetRepresentation().self() returns the Python widget object of sampledata module.

How to systematically execute custom python code at startup ?

Each time Slicer starts, it will look up for a file named .slicerrc.py in your HOME folder. (See What is my HOME folder ?)

Alternatively, set an environment variable named SLICERRC to the full path of a Python file to run at startup.

You can see the path to your .slicerrc.py file and edit it if you start Slicer and open in the menu: Edit / Application Settings. Application startup script is in the General section.

How to save an image/volume using python ?

The module slicer.util provides methods allowing to save either a node or an entire scene:

  • saveNode
  • saveScene

For more details see:

Enable or disable compression while saving a volume

While volumes can be accessed in Slicer Python modules as vtkMRMLVolumeNode, compression preference (or any other property for that matter) should be passed to slicer.util.saveNode function. The property will be passed to Slicer's storage node. For compression set the useCompression to 0 or 1. Example script:

properties['useCompression'] = 0; #do not compress
file_path = os.path.join(case_dir, file_name)
slicer.util.saveNode(node, file_path, properties)

How to assign a volume to a Slice view ?

Assuming the MRHead sample data has been loaded, you could do the following:

red_logic = slicer.app.layoutManager().sliceWidget("Red").sliceLogic()
red_cn = red_logic.GetSliceCompositeNode()
red_logic.GetSliceCompositeNode().SetBackgroundVolumeID(slicer.util.getNode('MRHead').GetID())

Discussion: http://slicer-devel.65872.n3.nabble.com/Assign-volumes-to-views-tt4028694.html

How to access vtkRenderer in Slicer 3D view ?

renderer = slicer.app.layoutManager().threeDWidget(0).threeDView().renderWindow().GetRenderers().GetFirstRenderer()

How to get VTK rendering backend ?

backend = slicer.app.layoutManager().threeDWidget(0).threeDView().renderWindow().GetRenderingBackend()

How to access displayable manager associated with a Slicer 2D or 3D view ?

As originally explained here, you could use the method getDisplayableManagers() available in any qMRMLThreeDView and qMRMLSliceView.

lm = slicer.app.layoutManager()
for v in range(lm.threeDViewCount):
  td = lm.threeDWidget(v)
  ms = vtk.vtkCollection()
  td.getDisplayableManagers(ms)
  for i in range(ms.GetNumberOfItems()):
   m = ms.GetItemAsObject(i)
   if m.GetClassName() == "vtkMRMLModelDisplayableManager":
     print(m)

How to center the 3D view on the scene ?

layoutManager = slicer.app.layoutManager()
threeDWidget = layoutManager.threeDWidget(0)
threeDView = threeDWidget.threeDView()
threeDView.resetFocalPoint()

Should I use 'old style' or 'new style' python classes in my scripted module ?

When python classes have no superclass specified they are 'old style' as described here [1].

In general it doesn't matter for the classes in a scripted module, since they won't be subclassed either old or new style should be the same.

For other python code in slicer where you might be subclassing, it's better to use new style classes. See the class hierarchies in the EditorLib and the DICOMLib for examples.

How to harden a transform ?

>>> n = getNode('Bone')
>>> logic = slicer.vtkSlicerTransformLogic()
>>> logic.hardenTransform(n)

Discussion: http://slicer-devel.65872.n3.nabble.com/Scripting-hardened-transforms-tt4029456.html

Where can I find example scripts?

Have a look at Documentation/Nightly/ScriptRepository.

How can I use a visual debugger for step-by-step debugging

Debugging using PyCharm or PyDev

Visual debugging (setting breakpoints, execute code step-by-step, view variables, stack, etc.) of Python scripted module is possible by using PyCharm or PyDev by using the Python debugger module Debugging tools extension.

See detailed instructions at the Debugging tools extension page.

Visual debugging of Python modules in Slicer

Debugging using Visual Studio

On Windows, Python Tools for Visual Studio (PTVS) enables debugging Python inside Visual Studio. Its remote debugging capability allows attaching the debugger to Slicer's embedded Python environment.

See Debugging Python in Visual Studio for details.

Debugging using remote-pdb

A command line debugging session with a pdb interface can be achieved with python-remote-pdb.

Install python-remote-pdb into Slicer's Python:

 git clone https://github.com/ionelmc/python-remote-pdb.git
 cd python-remote-pdb
 /path/to/Slicer-build/Slicer-build/Slicer ./setup.py install

Then, call set_trace() where you want to start the debugger.

 from remote_pdb import set_trace; set_trace()

In the console where Slicer was started, a message like the following will be printed:

 RemotePdb session open at 127.0.0.1:1234, waiting for connection

In another terminal, connect with telnet:

 telnet 127.0.0.1 1234

or socat (has history, readline support):

 socat readline tcp:127.0.0.1:1234

Why can't I access my C++ Qt class from python

  • Python wrapping of a Qt class requires a Qt style constructor with QObject as argument (it can be defaulted to null though), which is public. If one of these are missing, python wrapping will fail for that class
  • [Other reasons go here]

Can I use factory method like CreateNodeByClass or GetNodesByClass ?

See Documentation/Nightly/Developers/Tutorials/MemoryManagement#Factory_methods

How can I access callData argument in a VTK object observer callback function

To get notification about an event emitted by a VTK object you can simply use the AddObserver method, for example:

def sceneModifiedCallback(caller, eventId):
  print("Scene modified")
  print("There are {0} nodes in the scene". format(slicer.mrmlScene.GetNumberOfNodes()))

sceneModifiedObserverTag = slicer.mrmlScene.AddObserver(vtk.vtkCommand.ModifiedEvent, sceneModifiedCallback)

If an event also contains additional information as CallData then the type of this argument has to be specified as well, for example:

@vtk.calldata_type(vtk.VTK_OBJECT)
def nodeAddedCallback(caller, eventId, callData):
  print("Node added")
  print("New node: {0}".format(callData.GetName()))

nodeAddedModifiedObserverTag = slicer.mrmlScene.AddObserver(slicer.vtkMRMLScene.NodeAddedEvent, nodeAddedCallback)

Note: @vtk.calldata_type is a Python decorator, which modifies properties of a function that is declared right after the decorator. The decorator is defined in VTK (in Wrapping\Python\vtk\util\misc.py).

Usage from a class requires an extra step of creating the callback in the class __init__ function, as Python2 by default does some extra wrapping (http://stackoverflow.com/questions/9523370/adding-attributes-to-instance-methods-in-python):

class MyClass:
  def __init__(self):
    from functools import partial
    def nodeAddedCallback(self, caller, eventId, callData):
      print("Node added")
      print("New node: {0}".format(callData.GetName()))
    self.nodeAddedCallback = partial(nodeAddedCallback, self)
    self.nodeAddedCallback.CallDataType = vtk.VTK_OBJECT
  def registerCallbacks(self):
    self.nodeAddedModifiedObserverTag = slicer.mrmlScene.AddObserver(slicer.vtkMRMLScene.NodeAddedEvent, self.nodeAddedCallback)
  def unregisterCallbacks(self):
    slicer.mrmlScene.RemoveObserver(self.nodeAddedModifiedObserverTag)
        
myObject = MyClass()
myObject.registerCallbacks()

Allowed CallDataType values: VTK_STRING, VTK_OBJECT, VTK_INT, VTK_LONG, VTK_DOUBLE, VTK_FLOAT, "string0". See more information here: https://github.com/Kitware/VTK/blob/master/Wrapping/PythonCore/vtkPythonCommand.cxx

A simplified syntax is available by using a mix-in:

from slicer.util import VTKObservationMixin

class MyClass(VTKObservationMixin):
  def __init__(self):
    VTKObservationMixin.__init__(self)
    self.addObserver(slicer.mrmlScene, slicer.vtkMRMLScene.NodeAddedEvent, self.nodeAddedCallback)
  
  @vtk.calldata_type(vtk.VTK_OBJECT)
  def nodeAddedCallback(self, caller, eventId, callData):
    print("Node added")
    print("New node: {0}".format(callData.GetName()))

myObject = MyClass()

Note: VTKObservationMixin is a Python mix-in that allows adding a set of methods to a class by inheritance. VTKObservationMixin includes addObserver, hasObserver, observer, removeObserver, removeObservers methods, defined in Slicer (in Base\Python\slicer\util.py). For example of usage, see test_slicer_util_VTKObservationMixin.py

How to run CLI module from Python?

See here.

How can I run slicer operations from a batch script?

Slicer --no-main-window --python-script /tmp/test.py

Contents of /tmp/test.py

# use a slicer scripted module logic
from SampleData import SampleDataLogic
SampleDataLogic().downloadMRHead()
head = slicer.util.getNode('MRHead')

# use a vtk class
threshold = vtk.vtkImageThreshold()
threshold.SetInputData(head.GetImageData())
threshold.ThresholdBetween(100, 200)
threshold.SetInValue(255)
threshold.SetOutValue(0)

#  use a slicer-specific C++ class
erode = slicer.vtkImageErode()
erode.SetInputConnection(threshold.GetOutputPort())
erode.SetNeighborTo4()  
erode.Update()          

head.SetAndObserveImageData(erode.GetOutputDataObject(0))

slicer.util.saveNode(head, "/tmp/eroded.nrrd")

exit()

How can I run Slicer on a headless compute node?

Many cluster nodes are installed with minimal linux systems that don't include X servers. X servers, particularly those with hardware acceleration traditionally needed to be installed with root privileges, making it impossible to run applications that rendered using X or OpenGL.

But there is a workaround which allows everything in slicer to work normally so you could even do headless rendering.

You can use a modern version of X that supports running a dummy framebuffer. This can be installed in user mode so you don't even need to have root on the system.

See [2] for details.

There's a thread here with more discussion: [3]

Here is a working example of the approach running on a headless compute node running CTK tests (which also use Qt and VTK)

[4]