Difference between revisions of "Documentation/Nightly/Developers/Python scripting"

From Slicer Wiki
Jump to: navigation, search
Tag: 2017 source edit
 
(34 intermediate revisions by 5 users not shown)
Line 1: Line 1:
 
<noinclude>{{documentation/versioncheck}}</noinclude>
 
<noinclude>{{documentation/versioncheck}}</noinclude>
 +
 +
= Start Here =
 +
 +
Please read [https://docs.google.com/presentation/d/1JXIfs0rAM7DwZAho57Jqz14MRn2BIMrjB17Uj_7Yztc/edit?usp=sharing these slides] and [https://www.slicer.org/wiki/Documentation/Nightly/Training#PerkLab.27s_Slicer_bootcamp_training_materials these slides].  This will give you an overall idea what is possible, and you can use a binary download of Slicer to work through the example code. 
 +
 +
Refer to [http://www.na-mic.org/Wiki/index.php/2013_Project_Week_Breakout_Session:Slicer4Python this description that includes links to all the documentation].
 +
 +
Look at the [https://slicer.readthedocs.io/en/latest/developer_guide/script_repository.html Script repository] for examples and inspiration.
 +
 
= Background =
 
= Background =
  
Line 8: Line 17:
 
See [http://www.na-mic.org/Wiki/index.php/AHM2012-Slicer-Python this 2012 presentation on the state of python in slicer4].
 
See [http://www.na-mic.org/Wiki/index.php/AHM2012-Slicer-Python this 2012 presentation on the state of python in slicer4].
  
'''See [[4.0/Training#Slicer4_Programming_Tutorial|the python slicer4 tutorial for more examples]].'''
+
'''See [[Documentation/{{documentation/currentversion}}/Training#Slicer4_Programming_Tutorial|the python slicer4 tutorial for more examples]].'''
  
 
[[Documentation/{{documentation/version}}/Developers/Tutorials/SelfTestModule|Slicer Self Tests]] can be written in python, and provide a good source of examples for manipulating the data, logic, and gui of slicer.
 
[[Documentation/{{documentation/version}}/Developers/Tutorials/SelfTestModule|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=
 
= Start Here for Scripted Module and Extension Development=
An extensive tutorial and reference page was created [http://www.na-mic.org/Wiki/index.php/2013_Project_Week_Breakout_Session:Slicer4Python for the Slicer/Python breakout session at the NA-MIC 2014 Summer Project Week].
+
An extensive tutorial and reference page was created [http://www.na-mic.org/Wiki/index.php/2013_Project_Week_Breakout_Session:Slicer4Python for the Slicer/Python breakout session at the NA-MIC 2013 Summer Project Week].
  
 
= Usage options =
 
= Usage options =
  
==Python Interactor==
+
Slicer is an application that embeds a Python interpreter, similarly to [https://docs.blender.org/api/blender_python_api_current/info_overview.html Blender], [https://www.freecadweb.org/wiki/Embedding_FreeCAD FreeCAD], [https://manual.calibre-ebook.com/develop.html#using-an-interactive-python-interpreter Calibre], [https://developers.maxon.net/docs/Cinema4DPythonSDK/html/manuals/introduction/python_in_c4d.html Cinema4D], etc.
 +
 
 +
While Slicer has a set of core libraries that could be packaged in a way so that they can be imported in any Python environment (<code>import slicer</code>), but currently we don't offer this option. This exists only as limited, experimental option for most other applications, too.
 +
 
 +
We rather focus our efforts on other kind of Python integrations:
 +
 
 +
* Official, recommended way of running Python processing scripts is to execute them using Slicer's embedded Python interpreter (e.g., the Python interactor). Any additional required python packages can be installed using pip (<code>pip_install('scipy'</code>).
 +
* Python debuggers (PyCharm, VS Code, Eclipse, etc.) can attach to a running Slicer application.
 +
* Slicer is accessible from Jupyter notebooks - SlicerJupyter extension makes Slicer application act as a Jupyter kernel, while keeping the application fully interactive.
 +
* Slicer can launch any external Python scripts (GUI is generated automatically from XML descriptor), running it by default in a background processing thread, without blocking the GUI. See example [https://github.com/lassoan/SlicerPythonCLIExample here].
 +
 
 +
=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.
 
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.
Line 23: Line 43:
 
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.
 
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 ===
+
== Examples ==
  
Start slicer4 and bring up python console.  Load a volume like this:
+
Start Slicer and bring up python console.  Load a sample volume like this:
  
 
<pre>
 
<pre>
>>> slicer.util.loadVolume(slicer.app.slicerHome + "/share/MRML/Testing/TestData/fixed.nrrd")
+
import SampleData
 +
sampleDataLogic = SampleData.SampleDataLogic()
 +
sampleDataLogic.downloadMRHead()
 
</pre>
 
</pre>
  
Line 34: Line 56:
  
 
<pre>
 
<pre>
>>> n = getNode('fixed')
+
n = getNode('MRHead')
 
</pre>
 
</pre>
  
 
You can use Tab to see lists of methods for a class instance.
 
You can use Tab to see lists of methods for a class instance.
  
==== Accessing Volume data as numpy array ====
+
=== 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:
 
You can easily inspect and manipulate volume data using numpy and related code.  In slicer you can do this:
  
 
<pre>
 
<pre>
>>> a = array('fixed')
+
a = array('MRHead')
 
</pre>
 
</pre>
  
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.
+
and 'a' will be a pointer to the appropriate data (no data copying). If you get an error that 'array' is not defined then run 'import slicer.util' and use 'slicer.util.array'. Scalar volumes become three-dimensional arrays, while vector volumes become 4D, and tensor volumes are 5D.  All arrays can be manipulated directly. After modification is completed, call Modified() method of the volume node to indicate that the image is modified and trigger display update.
 
 
 
 
==== Running a CLI from Python ====
 
  
Here's an example to create a model from a volume using the [[Documentation/4.0/Modules/GrayscaleModelMaker|Grayscale Model Maker]]
+
The '''array''' method is intended for quick testing only, as multiple nodes may have the same name and various arrays may be retrieved from MRML nodes. In Slicer modules, it is recommended to use '''arrayFromVolume''' instead, which takes a MRML node as input.
<pre>
 
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.run(grayMaker, None, parameters))
 
</pre>
 
  
To try this, download the MRHead dataset from the [[Documentation/4.0/Modules/SampleData|Sample Data]] and paste the code into the python console and then run this:
 
 
<pre>
 
<pre>
v = getNode('MRHead')
+
volumeNode = getNode('MRHead')
cliNode = grayModel(v)
+
a = arrayFromVolume(volumeNode)
 +
# Increase image contrast
 +
a[:] = a * 2.0
 +
arrayFromVolumeModified(volumeNode)
 
</pre>
 
</pre>
  
Note that the CLI module runs in a background thread, so the call to grayModel will return right away.  But the slicer.cli.run call returns a cliNode (an instance of [http://slicer.org/doc/html/classvtkMRMLCommandLineModuleNode.html vtkMRMLCommandLineModuleNode]) which can be used to monitor the progress of the module.
+
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, using '''updateVolumeFromArray''':
 
 
'' Passing Fiducials to CLIs via a Python Script ''
 
  
 
<pre>
 
<pre>
 +
import numpy as np
 +
import math
  
import SampleData
+
def some_func(x, y, z):
sampleDataLogic = SampleData.SampleDataLogic()
+
  return 0.5*x*x + 0.3*y*y + 0.5*z*z
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}
+
a = np.fromfunction(some_func,(30,20,15))
 
 
cliNode = slicer.cli.run(slicer.modules.simpleregiongrowingsegmentation, None, params , wait_for_completion=True)
 
  
 +
volumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLScalarVolumeNode')
 +
volumeNode.CreateDefaultDisplayNodes()
 +
updateVolumeFromArray(volumeNode, a)
 +
setSliceViewerLayers(background=volumeNode)
 
</pre>
 
</pre>
  
'' Checking Status''
+
=== Accessing Model data as numpy array ===
  
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.
+
You can easily inspect and manipulate point coordinates of a model using numpy and related code by calling `arrayFromModelPoints`. After modification is completed, call Modified() method on the polydata to indicate that the model is modified and trigger display update.
  
 
<pre>
 
<pre>
def printStatus(caller, event):
+
# Create a model containing a sphere
  print("Got a %s from a %s" % (event, caller.GetClassName()))
+
sphere = vtk.vtkSphereSource()
  if caller.IsA('vtkMRMLCommandLineModuleNode'):
+
sphere.SetRadius(30.0)
    print("Status is %s" % caller.GetStatusString())
+
sphere.Update()
 +
modelNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLModelNode')
 +
modelNode.SetAndObservePolyData(sphere.GetOutput())
 +
modelNode.CreateDefaultDisplayNodes()
 +
a = arrayFromModelPoints(modelNode)
 +
# change Y scaling
 +
a[:,2] = a[:,2] * 2.5
 +
arrayFromModelPointsModified(modelNode)
 +
</pre>
 +
 
 +
=== Running a CLI from Python ===
  
cliNode.AddObserver('ModifiedEvent', printStatus)
+
[https://slicer.readthedocs.io/en/latest/developer_guide/python_faq.html#how-to-run-a-cli-module-from-python This documentation section has moved to ReadTheDocs.]
</pre>
 
  
 
==== Accessing slice vtkRenderWindows from slice views ====
 
==== Accessing slice vtkRenderWindows from slice views ====
Line 125: Line 138:
 
</pre>
 
</pre>
  
''TODO: some more samples of the Qt console''
+
= Script Repository =  
 
 
== In iPython ==
 
 
 
'''Important: The example below was developed for an early beta version of slicer4 and is not supported in Slicer 4.0 or 4.1'''
 
 
 
See [http://slicer-devel.65872.n3.nabble.com/import-slicer-problem-in-using-python-in-the-shell-launched-using-quot-slicer-xterm-amp-quot-td3968880.html this thread for information on adapting this approach to Slicer 4.1].
 
 
 
 
 
[http://ipython.scipy.org iPython] is a powerful shell and can also be used to access the vtk and slicer APIs (but not Qt at the moment).
 
 
 
As of Slicer4 beta in February 2011, it is possible to use these steps for installation.  This has only been tested on a ubuntu linux system so far.
 
 
 
These instructions assume you have a Slicer4-superbuild directory created according to the [[Slicer4:Build_Instructions|Slicer4 Build Instructions]].
 
 
 
See the [http://matplotlib.sourceforge.net|matplotlib] website for more example plot types.
 
 
 
=== Prerequisites ===
 
 
 
* Get readline - it will make iPython more useful.
 
 
 
# do this before building slicer4 - or do "cd Slicer4-superbuild/; rm -rf python* ; make"
 
sudo apt-get install libreadline6-dev
 
 
 
cd to your Slicer4-superbuild directory for the rest of these steps
 
 
 
* Install the vtk package in the python tree
 
# TODO: this should be done in superbuild script
 
(cd ./VTK-build/Wrapping/Python;  ../../../Slicer-build/Slicer4 --launch ../../../python-build/bin/python setup.py install)
 
 
 
* Install ipython:
 
 
 
git clone git://github.com/ipython/ipython.git
 
(cd ./ipython; git checkout 0.10.2)
 
(cd ./ipython;  ../Slicer-build/Slicer4 --launch ../python-build/bin/python setup.py install)
 
 
 
* Install matplotlib (remove the source after installing so python import will not get confused by it.)
 
 
 
git clone git://github.com/pieper/matplotlib.git
 
(cd ./matplotlib;  ../Slicer-build/Slicer4 --launch ../python-build/bin/python setup.py install)
 
rm -rf matplotlib
 
 
 
=== Now try it! ===
 
[[image:Slicer4-matplotlib-2011-02-26.png|thumb|300px|right|python plotting from ipython using slicer libraries]]
 
 
 
Launch an xterm with all the paths set correctly to find the slicer python packages
 
 
 
./Slicer-build/Slicer4 --launch xterm &
 
 
 
Now, ''inside the xterm'' launch ipython
 
 
 
./python-build/bin/ipython
 
 
 
Inside ipython you can past the following script that does:
 
* create a mrml scene and add a volume
 
* make a numpy aray from the image data
 
* do calculations in numpy and vtk for comparision
 
* make a histogram plot of the data
 
 
 
 
 
<pre>
 
 
 
import vtk
 
import slicer
 
mrml = slicer.vtkMRMLScene()
 
vl = slicer.vtkSlicerVolumesLogic()
 
vl.SetAndObserveMRMLScene(mrml)
 
n = vl.AddArchetypeVolume('../Slicer4/Testing/Data/Input/MRHeadResampled.nhdr', 'CTC')
 
i = n.GetImageData()
 
print (i.GetScalarRange())
 
 
 
import vtk.util.numpy_support
 
a = vtk.util.numpy_support.vtk_to_numpy(i.GetPointData().GetScalars())
 
print(a.min(),a.max())
 
 
 
import matplotlib
 
import matplotlib.pyplot
 
n, bins, patches = matplotlib.pyplot.hist(a, 50, facecolor='g', alpha=0.75)
 
matplotlib.pyplot.show()
 
 
 
</pre>
 
 
 
If all goes well, you should see an image like the one shown here.
 
 
 
=== Parallel Processing ===
 
 
 
In the shell, run this to install [http://packages.python.org/joblib/ joblib]
 
 
 
wget http://pypi.python.org/packages/source/j/joblib/joblib-0.4.6.dev.tar.gz
 
tar xvfz joblib-0.4.6.dev.tar.gz
 
(cd ./joblib-0.4.6.dev;  ../Slicer-build/Slicer4 --launch ../python-build/bin/python setup.py install)
 
 
 
Then in ipython you can run this example:
 
 
 
from joblib import *
 
from math import *
 
for jobs in xrange(1,8):
 
  print (jobs, Parallel(n_jobs=jobs)(delayed(sqrt)(i**2) for i in range(10000))[-1])
 
 
 
=== Packaging/Saving Scenes ===
 
 
 
A basic example.  The following packages all of the scene's referenced files into one specified folder.  All images, csvs, volumes, etc. (everything except the scene .mrml) are copied into the folder "<package dir>/data."  The scene .mrml is in the "<package dir>" directory.
 
 
 
import os
 
# Library for OS specific routines
 
 
tempDir = os.path.join(slicer.app.slicerHome, ‘testScene’)
 
# Put our temp scene directory into the slicer directory.  os.path.join takes care of slash issues that you may encounter with UNIX-Windows compatibility.   
 
 
os.mkdir(tempDir)
 
 
 
l = slicer.app.applicationLogic()
 
l.SaveSceneToSlicerDataBundleDirectory(tempDir, None)
 
 
 
=== Loading DICOM Sets ===
 
 
 
The approach is to point Slicer's DICOM database to the directory of the new files.  The command appends the existing database with the files found in the inputted directory.
 
i = ctk.ctkDICOMIndexer()
 
i.addDirectory(slicer.dicomDatabase, '/yourDICOMdir/')
 
 
 
One approach to begin the load process is to call on the DICOM module, which will automatically open the "DICOM Details" popup.  However, if the popup has been used already in the current Slicer session, a refresh may not occur and a restart may be required.
 
m = slicer.util.mainWindow()
 
m.moduleSelector().selectModule('DICOM')
 
 
 
= Resources =
 
== Installing Pip ==
 
 
 
In a nutshell, both distribute and pip will have to be installed.
 
 
 
1. Download distribute: http://python-distribute.org/distribute_setup.py
 
 
 
2. Install distribute:
 
 
 
$ Slicer distribute_setup.py
 
 
 
<pre>
 
Number of registered modules: 1
 
Number of instantiated modules: 1
 
Number of loaded modules: 1
 
Loading Slicer RC file [/home/jchris/.slicerrc.py]
 
Slicer RC file loaded [09/01/2013 20:23:41]
 
Downloading http://pypi.python.org/packages/source/d/distribute/distribute-0.6.34.tar.gz
 
Extracting in /tmp/tmpW05FBr
 
Now working in /tmp/tmpW05FBr/distribute-0.6.34
 
Installing Distribute
 
File  "/tmp/tmpW05FBr/distribute-0.6.34/qSlicerBaseQTCore_fr.qm"  doesn't exist.
 
Number of registered modules: 1
 
Number of instantiated modules: 1
 
Number of loaded modules: 1
 
Loading Slicer RC file [/home/jchris/.slicerrc.py]
 
Slicer RC file loaded [09/01/2013 20:24:29]
 
Before install bootstrap.
 
Scanning installed packages
 
No setuptools distribution found
 
running install
 
running bdist_egg
 
running egg_info
 
writing distribute.egg-info/PKG-INFO
 
writing top-level names to distribute.egg-info/top_level.txt
 
[...]
 
Installed /home/jchris/Projects/Slicer-AHM-Superbuild-Debug/python-build/lib/python2.6/site-packages/distribute-0.6.34-py2.6.egg
 
Processing dependencies for distribute==0.6.34
 
Finished processing dependencies for distribute==0.6.34
 
After install bootstrap.
 
Creating /home/jchris/Projects/Slicer-AHM-Superbuild-Debug/python-build/lib/python2.6/site-packages/setuptools-0.6c11-py2.6.egg-info
 
Creating /home/jchris/Projects/Slicer-AHM-Superbuild-Debug/python-build/lib/python2.6/site-packages/setuptools.pth
 
</pre>
 
 
 
3. Download pip: https://raw.github.com/pypa/pip/master/contrib/get-pip.py
 
 
 
4. Install pip:
 
 
 
  $ Slicer get-pip.py
 
 
 
<pre>
 
Number of registered modules: 1
 
Number of instantiated modules: 1
 
Number of loaded modules: 1
 
Loading Slicer RC file [/home/jchris/.slicerrc.py]
 
Slicer RC file loaded [09/01/2013 20:41:29]
 
Downloading/unpacking pip
 
  Running setup.py egg_info for package pip
 
    Number of registered modules: 1
 
    Number of instantiated modules: 1
 
    Number of loaded modules: 1
 
    Loading Slicer RC file [/home/jchris/.slicerrc.py]
 
    Slicer RC file loaded [09/01/2013 20:41:38]
 
    warning: manifest_maker: standard file '' not found
 
    warning: no files found matching '*.html' under directory 'docs'
 
    warning: no previously-included files matching '*.txt' found under directory 'docs/_build'
 
    no previously-included directories found matching 'docs/_build/_sources'
 
Installing collected packages: pip
 
  Running setup.py install for pip
 
    Number of registered modules: 1
 
    Number of instantiated modules: 1
 
    Number of loaded modules: 1
 
    Loading Slicer RC file [/home/jchris/.slicerrc.py]
 
    Slicer RC file loaded [09/01/2013 20:41:41]
 
    warning: manifest_maker: standard file '' not found
 
    warning: no files found matching '*.html' under directory 'docs'
 
    warning: no previously-included files matching '*.txt' found under directory 'docs/_build'
 
    no previously-included directories found matching 'docs/_build/_sources'
 
    Installing pip script to /home/jchris/Projects/Slicer-AHM-Superbuild-Debug/python-build/bin
 
    Installing pip-2.6 script to /home/jchris/Projects/Slicer-AHM-Superbuild-Debug/python-build/bin
 
Successfully installed pip
 
Cleaning up...
 
</pre>
 
 
 
For additional information regarding installation of pip. See http://www.pip-installer.org/en/latest/installing.html
 
 
 
== Using Pip ==
 
 
 
1. Within the Slicer python interactor:
 
 
 
  ./Slicer --no-main-window --disable-cli-modules --disable-loadable-modules --disable-scripted-loadable-modules --show-python-interactor
 
 
 
2. Define the function <code>install_distributions</code>
 
 
 
<pre>
 
def install_distributions(distributions):
 
  """
 
  Copied from http://threebean.org/blog/2011/06/06/installing-from-pip-inside-python-or-a-simple-pip-api/
 
  """
 
  import pip.commands.install
 
  command = pip.commands.install.InstallCommand()
 
  opts, args = command.parser.parse_args()
 
  # TBD, why do we have to run the next part here twice before actual install
 
  requirement_set = command.run(opts, distributions)
 
  requirement_set = command.run(opts, distributions)
 
  requirement_set.install(opts)
 
</pre>
 
 
 
3. Try to install a package
 
 
 
install_distributions(["Markdown"])
 
 
 
=Issues=
 
* matplotlib currently uses Tk to show the window on Linux and it does not handle pan/zoom events correctly.  Ideally there would be a PythonQt wrapper for the plots and this is probably required for use on windows (and maybe mac).
 
 
 
* the matplotlib window is in the same thread with the ipython window so you cannot keep the plot open while working on the next one.  However you can save the plot to a file (png, pdf, etc...) and look at it with another program while working in ipython.
 
 
 
* in slicer4 the PythonQt package is loaded as 'qt', however matplotlib tries running 'import qt' as a way to determine if it is running in PyQt version 3.  Because of this a patched version of matplotlib is required (see [https://github.com/pieper/matplotlib/commit/23bca600c27924450128bfe6e68196eb87cf0654 this diff])
 
 
 
* Tested in Windows 7: Python 2.6.6 (used in Slicer 4.1) has errors in referencing its XML DOM parsers, such as ElementTree.  This is a Windows-specific issue -- the same code works in Linux Ubuntu.  Solution thus far is to write your own XML parser.
 
  
 +
See [https://slicer.readthedocs.io/en/latest/developer_guide/script_repository.html Script repository] for a larger collection of example code.
  
 
{{:Documentation/{{documentation/version}}/Developers/FAQ/Python Scripting|Python Scripting}}
 
{{:Documentation/{{documentation/version}}/Developers/FAQ/Python Scripting|Python Scripting}}

Latest revision as of 04:09, 24 April 2021

Home < Documentation < Nightly < Developers < Python scripting


For the latest Slicer documentation, visit the read-the-docs.


Contents

Start Here

Please read these slides and these slides. This will give you an overall idea what is possible, and you can use a binary download of Slicer to work through the example code.

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

Look at the Script repository for examples and inspiration.

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

Slicer is an application that embeds a Python interpreter, similarly to Blender, FreeCAD, Calibre, Cinema4D, etc.

While Slicer has a set of core libraries that could be packaged in a way so that they can be imported in any Python environment (import slicer), but currently we don't offer this option. This exists only as limited, experimental option for most other applications, too.

We rather focus our efforts on other kind of Python integrations:

  • Official, recommended way of running Python processing scripts is to execute them using Slicer's embedded Python interpreter (e.g., the Python interactor). Any additional required python packages can be installed using pip (pip_install('scipy').
  • Python debuggers (PyCharm, VS Code, Eclipse, etc.) can attach to a running Slicer application.
  • Slicer is accessible from Jupyter notebooks - SlicerJupyter extension makes Slicer application act as a Jupyter kernel, while keeping the application fully interactive.
  • Slicer can launch any external Python scripts (GUI is generated automatically from XML descriptor), running it by default in a background processing thread, without blocking the GUI. See example here.

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 Slicer and bring up python console. Load a sample volume like this:

import SampleData
sampleDataLogic = SampleData.SampleDataLogic()
sampleDataLogic.downloadMRHead()

Get the volume node for that volume:

n = getNode('MRHead')

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('MRHead')

and 'a' will be a pointer to the appropriate data (no data copying). If you get an error that 'array' is not defined then run 'import slicer.util' and use 'slicer.util.array'. Scalar volumes become three-dimensional arrays, while vector volumes become 4D, and tensor volumes are 5D. All arrays can be manipulated directly. After modification is completed, call Modified() method of the volume node to indicate that the image is modified and trigger display update.

The array method is intended for quick testing only, as multiple nodes may have the same name and various arrays may be retrieved from MRML nodes. In Slicer modules, it is recommended to use arrayFromVolume instead, which takes a MRML node as input.

volumeNode = getNode('MRHead')
a = arrayFromVolume(volumeNode)
# Increase image contrast
a[:] = a * 2.0
arrayFromVolumeModified(volumeNode)

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, using updateVolumeFromArray:

import numpy as np
import math

def some_func(x, y, z):
  return 0.5*x*x + 0.3*y*y + 0.5*z*z

a = np.fromfunction(some_func,(30,20,15))

volumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLScalarVolumeNode')
volumeNode.CreateDefaultDisplayNodes()
updateVolumeFromArray(volumeNode, a)
setSliceViewerLayers(background=volumeNode)

Accessing Model data as numpy array

You can easily inspect and manipulate point coordinates of a model using numpy and related code by calling `arrayFromModelPoints`. After modification is completed, call Modified() method on the polydata to indicate that the model is modified and trigger display update.

# Create a model containing a sphere
sphere = vtk.vtkSphereSource()
sphere.SetRadius(30.0)
sphere.Update()
modelNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLModelNode')
modelNode.SetAndObservePolyData(sphere.GetOutput())
modelNode.CreateDefaultDisplayNodes()
a = arrayFromModelPoints(modelNode)
# change Y scaling
a[:,2] = a[:,2] * 2.5
arrayFromModelPointsModified(modelNode)

Running a CLI from Python

This documentation section has moved to ReadTheDocs.

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 Script repository for a larger collection of example code.


Developer FAQ: Python Scripting

How to run pip ?

The pip executable is not distributed, instead the following command should be used:

  • from build tree: /path/to/Slicer-SuperBuild/python-install/bin/PythonSlicer -m pip ...
  • from install tree:
 * Linux/MacOS: /path/to/Slicer-X.Y.Z-plat-arch/bin/PythonSlicer -m pip ...
 * Windows: "c:\Program Files\Slicer 4.10.0\bin\PythonSlicer.exe" -m pip ...


See this discussion for more details and background: https://discourse.slicer.org/t/slicer-python-packages-use-and-install/984/29.

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 ?

See https://slicer.readthedocs.io/en/latest/user_guide/settings.html#application-startup-file

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 the Script repository.

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

Visual debugging (setting breakpoints, execute code step-by-step, view variables, stack, etc.) of Python scripted module is possible by using various IDEs. See detailed setup instructions here.

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
  • You cannot access your custom C++ Qt classes from python outside of the scope of your instantiated python class. These will not work:
BarIDRole = slicer.qFooItemDelegate.LastRole + 1

class BarTableWidget(qt.QTableWidget, VTKObservationMixin):

    def __init__(self, *args, **kwargs):
        [...]
class BarTableWidget(qt.QTableWidget, VTKObservationMixin):

    BarIDRole = slicer.qFooItemDelegate.LastRole + 1

    def __init__(self, *args, **kwargs):
        [...]

Instead, do:

class BarTableWidget(qt.QTableWidget, VTKObservationMixin):

    def __init__(self, *args, **kwargs):
        self.BarIDRole = slicer.qFooItemDelegate.LastRole + 1
        [...]
  • [Other reasons go here]

Can I use factory method like CreateNodeByClass or GetNodesByClass ?

See https://slicer.readthedocs.io/en/latest/developer_guide/advanced_topics.html#memory-management

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).

Note: The available types are listed in Wrapping\Python\vtkmodules\util\vtkConstants.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

For a simplified syntax, see #How_to_manage_VTK_object_connections_.3F

How to manage VTK object connections ?

The VTKObservationMixin is a Python mix-in that allows adding a set of methods to a class by inheritance. It includes the following methods defined in Base/Python/slicer/util.py:

  • addObserver
  • hasObserver
  • observer
  • removeObserver
  • removeObservers

The following illustrates how to observe the slicer.vtkMRMLScene.NodeAddedEvent:

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()

For additional examples of usage, see test_slicer_util_VTKObservationMixin.py

Slicer crashes if I try to access a non-existing item in an array

For example, this code makes Slicer crash:

s = vtk.vtkStringArray()
s.GetValue(0)

This behavior is expected, as all VTK objects are implemented in C++ that offers much faster operation but developers have to take care of addressing only valid array elements, for example by checking the number of elements in the array:

if itemIndex < 0 or itemIndex >= s.GetNumberOfValues()
  raise IndexError("index out of bounds")


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]

How to save user's selection of parameters and nodes in the scene?

It is preferable to save all the parameter values and nodes selections that the user made on the user interface into the MRML scene. This allows the user to load a scene and continue from where he left off. These information can be saved in a slicer.vtkMRMLScriptedModuleNode() node.

For example:

parameterNode=slicer.vtkMRMLScriptedModuleNode()

# Save parameter values and node references to parameter node

alpha = 5.0
beta = "abc"
inputNode = slicer.util.getNode("InputNode")

parameterNode.SetParameter("Alpha",str(alpha))
parameterNode.SetParameter("Beta", beta)
parameterNode.SetNodeReferenceID("InputNode", inputNode.GetID())

# Retrieve parameter values and node references from parameter node

alpha = float(parameterNode.GetParameter("Alpha"))
beta = parameterNode.GetParameter("Beta")
inputNode = parameterNode.GetNodeReference("InputNode")

Scripted module's logic class have a helper function, getParameterNode, which returns a parameter node that is unique for a specific module. The function creates the parameter node if it has not been created yet. By default, the parameter node is a singleton node, which means that there is only a single instance of the node in the scene. If it is preferable to allow multiple instances of the parameter node, set isSingletonParameterNode member of the logic object to False.

How to load a UI file ?

See Documentation/Nightly/Developers/Tutorials/PythonAndUIFile

How to update progress bar in scripted (Python, or other) CLI modules

As detailed in the Execution Model documentation, Slicer parses specifically-formatted XML commands printed on stdout, to allow any out-of-process CLI program to report progress back to the main Slicer application (which will causing the progress bar to update). However, it is very important to note that the output must be flushed after each print statement, or else Slicer will not parse the progress sections until the process ends. See the calls to

sys.stdout.flush()

in the example Python CLI shown below:

#!/usr/bin/env python-real

if __name__ == '__main__':
  import time
  import sys
  
  print("""<filter-start><filter-name>TestFilter</filter-name><filter-comment>ibid</filter-comment></filter-start>""")
  sys.stdout.flush()

  for i in range(0,10):
      print("""<filter-progress>{}</filter-progress>""".format(i/10.0))
      sys.stdout.flush()
      time.sleep(0.5)

  print("""<filter-end><filter-name>TestFilter</filter-name><filter-time>10</filter-time></filter-end>""")
  sys.stdout.flush()

How to display progress bar for CLI module execution in a scripted module

def createProgressDialog(parent=None, value=0, maximum=100, windowTitle="Processing..."):
    import qt # qt.qVersion()
    progressIndicator = qt.QProgressDialog()  #(parent if parent else self.mainWindow())
    progressIndicator.minimumDuration = 0
    progressIndicator.maximum = maximum
    progressIndicator.value = value
    progressIndicator.windowTitle = windowTitle
    return progressIndicator
      

class MyModuleWidget(ScriptedLoadableModuleWidget, VTKObservationMixin):

    def setup(self)
        parametersFormLayout = qt.QFormLayout(myInputCollapsibleButton)
        self.testButton = qt.QPushButton('Test')
        self.testButton.enabled = True
        self.testButton.clicked.connect(self.onTestButton)
        parametersFormLayout.addRow(self.TestButton)
    
    def onTestButton(self):
        myCli = slicer.modules.tmp2cli
        cliNode = None
        myInt = 100
        cliNode = slicer.cli.run(myCli, cliNode, {'myString': 'hello World', 'myInt':100} )
        cliNode.AddObserver('ModifiedEvent', self.onCliModified)
        self.progressBar = myUtil.createProgressDialog(None, 0, myInt)
    
    def onCliModified(self, caller, event):
        self.progressBar.setValue(caller.GetProgress())
        if caller.GetStatus() == slicer.vtkMRMLCommandLineModuleNode.Completed: 
            self.progressBar.close()

See complete example here.

How to run Python script using a non-Slicer Python environment

If you need to run Python scripts using not Slicer's embedded interpreter but using a different environment (Python3, Anaconda, etc.) then you need to run the Python executable using a default startup environment. Starting from Slicer's environment would cause loading of Slicer's Python libraries, which are expected to be binary incompatible with the external environment and therefore would make the external application crash.

Example of running python code using system Python3 in Linux from Slicer:

command_line = ["/usr/bin/python3", "-c", "print('hola')"]
from subprocess import check_output
command_result = check_output(command_line, env=slicer.util.startupEnvironment())
print(command_result)

This example script just prints 'hola' and exits, but the code can be replaced by more complex instructions or launching of a Python script.

Example of running python code using Anaconda using environment named 'workspace-gpu' on Windows from Slicer:

command_line = [r"c:\Users\msliv\Miniconda3\envs\workspace-gpu\python.exe", "-c", "print('hola')"]
from subprocess import check_output
command_result = check_output(command_line, env=slicer.util.startupEnvironment())
print(command_result)

How to configure network proxy ?

When using either urllib.request or requests, proxy configured by setting http_proxy and https_proxy environment variables will automatically be used.

For more details: