Difference between revisions of "Documentation/Nightly/Developers/FAQ/Python Scripting"

From Slicer Wiki
Jump to: navigation, search
Tag: 2017 source edit
 
(3 intermediate revisions by the same user not shown)
Line 1: Line 1:
<noinclude>{{documentation/versioncheck}}</noinclude>
+
<noinclude>{{documentation/versioncheck}}
 +
</noinclude>
 
<noinclude>__TOC__
 
<noinclude>__TOC__
={{#titleparts: {{PAGENAME}} | | -1 }}=</noinclude><includeonly>
+
={{#titleparts: {{PAGENAME}} | | -1 }}=
 +
</noinclude><includeonly>
 
='''Developer FAQ: {{{1}}}'''=
 
='''Developer FAQ: {{{1}}}'''=
 
</includeonly>
 
</includeonly>
  
== How to run pip ? ==
+
==How to run pip ?==
  
 
The pip executable is not distributed, instead the following command should be used:
 
The pip executable is not distributed, instead the following command should be used:
* from build tree: <tt>/path/to/Slicer-SuperBuild/python-install/bin/PythonSlicer -m pip ...</tt>
+
 
* from install tree:
+
*from build tree: <tt>/path/to/Slicer-SuperBuild/python-install/bin/PythonSlicer -m pip ...</tt>
 +
*from install tree:
 +
 
 
   * Linux/MacOS: <tt>/path/to/Slicer-X.Y.Z-plat-arch/bin/PythonSlicer -m pip ...</tt>
 
   * Linux/MacOS: <tt>/path/to/Slicer-X.Y.Z-plat-arch/bin/PythonSlicer -m pip ...</tt>
 
   * Windows: <tt>"c:\Program Files\Slicer 4.10.0\bin\PythonSlicer.exe" -m pip ...</tt>
 
   * Windows: <tt>"c:\Program Files\Slicer 4.10.0\bin\PythonSlicer.exe" -m pip ...</tt>
Line 16: Line 20:
 
See this discussion for more details and background: https://discourse.slicer.org/t/slicer-python-packages-use-and-install/984/29.
 
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 ==
+
==How to access a scripted module from python scripts==
  
 
All slicer modules are accessible in the <code>slicer.modules</code> namespace. For example, ''sampledata'' module can be accessed as <code>slicer.modules.sampledata</code>.
 
All slicer modules are accessible in the <code>slicer.modules</code> namespace. For example, ''sampledata'' module can be accessed as <code>slicer.modules.sampledata</code>.
Line 22: Line 26:
 
To access a module's widget, use <code>widgetRepresentation()</code> method to get the C++ base class and its <code>self()</code> method to get the Python class. For example, <code>slicer.modules.sampledata.widgetRepresentation().self()</code> returns the Python widget object of ''sampledata'' module.
 
To access a module's widget, use <code>widgetRepresentation()</code> method to get the C++ base class and its <code>self()</code> method to get the Python class. For example, <code>slicer.modules.sampledata.widgetRepresentation().self()</code> returns the Python widget object of ''sampledata'' module.
  
== How to systematically execute custom python code at startup ? ==
+
==How to systematically execute custom python code at startup ?==
  
 
See https://slicer.readthedocs.io/en/latest/user_guide/settings.html#application-startup-file
 
See https://slicer.readthedocs.io/en/latest/user_guide/settings.html#application-startup-file
  
== How to save an image/volume using python ? ==
+
==How to save an image/volume using python ?==
  
 
The module <code>slicer.util</code> provides methods allowing to save either a node or an entire scene:
 
The module <code>slicer.util</code> provides methods allowing to save either a node or an entire scene:
* saveNode
+
 
* saveScene
+
*saveNode
 +
*saveScene
  
 
For more details see:
 
For more details see:
* https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/util.py#L229-267
 
* https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/tests/test_slicer_util_save.py
 
  
=== Enable or disable compression while saving a volume ===
+
*https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/util.py#L229-267
 +
*https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/tests/test_slicer_util_save.py
 +
 
 +
===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 <code>useCompression</code> to 0 or 1. Example script:
 
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 <code>useCompression</code> to 0 or 1. Example script:
Line 45: Line 51:
 
</pre>
 
</pre>
  
== How to assign a volume to a Slice view ? ==
+
==How to assign a volume to a Slice view ?==
  
 
Assuming the <code>MRHead</code> sample data has been loaded, you could do the following:
 
Assuming the <code>MRHead</code> sample data has been loaded, you could do the following:
Line 57: Line 63:
 
Discussion: http://slicer-devel.65872.n3.nabble.com/Assign-volumes-to-views-tt4028694.html
 
Discussion: http://slicer-devel.65872.n3.nabble.com/Assign-volumes-to-views-tt4028694.html
  
== How to access vtkRenderer in Slicer 3D view ? ==
+
==How to access vtkRenderer in Slicer 3D view ?==
  
  
Line 64: Line 70:
 
</pre>
 
</pre>
  
== How to get VTK rendering backend ? ==
+
==How to get VTK rendering backend ?==
  
 
<pre>
 
<pre>
Line 70: Line 76:
 
</pre>
 
</pre>
  
== How to access displayable manager associated with a Slicer 2D or 3D view ? ==
+
==How to access displayable manager associated with a Slicer 2D or 3D view ?==
  
 
As originally explained [http://slicer-devel.65872.n3.nabble.com/How-to-get-the-point-of-a-3D-model-based-on-the-fiducial-position-td4031760.html#a4031762 here], you could use the method <code>getDisplayableManagers()</code> available in any [{{doxygen-class-url|qMRMLThreeDView}} qMRMLThreeDView] and [{{doxygen-class-url|qMRMLSliceView}} qMRMLSliceView].
 
As originally explained [http://slicer-devel.65872.n3.nabble.com/How-to-get-the-point-of-a-3D-model-based-on-the-fiducial-position-td4031760.html#a4031762 here], you could use the method <code>getDisplayableManagers()</code> available in any [{{doxygen-class-url|qMRMLThreeDView}} qMRMLThreeDView] and [{{doxygen-class-url|qMRMLSliceView}} qMRMLSliceView].
Line 86: Line 92:
 
</pre>
 
</pre>
  
== How to center the 3D view on the scene ? ==
+
==How to center the 3D view on the scene ?==
  
 
<pre>
 
<pre>
Line 95: Line 101:
 
</pre>
 
</pre>
  
== Should I use 'old style' or 'new style' python classes in my scripted module ? ==
+
==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 [http://docs.python.org/2/reference/datamodel.html#new-style-and-classic-classes].
 
When python classes have no superclass specified they are 'old style' as described here [http://docs.python.org/2/reference/datamodel.html#new-style-and-classic-classes].
Line 103: Line 109:
 
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 [https://github.com/Slicer/Slicer/tree/master/Modules/Scripted/EditorLib EditorLib] and the [https://github.com/Slicer/Slicer/tree/master/Modules/Scripted/DICOM/DICOMLib DICOMLib] for examples.
 
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 [https://github.com/Slicer/Slicer/tree/master/Modules/Scripted/EditorLib EditorLib] and the [https://github.com/Slicer/Slicer/tree/master/Modules/Scripted/DICOM/DICOMLib DICOMLib] for examples.
  
== How to harden a transform ? ==
+
==How to harden a transform ?==
  
 
<pre>
 
<pre>
Line 113: Line 119:
 
Discussion: http://slicer-devel.65872.n3.nabble.com/Scripting-hardened-transforms-tt4029456.html
 
Discussion: http://slicer-devel.65872.n3.nabble.com/Scripting-hardened-transforms-tt4029456.html
  
== Where can I find example scripts? ==
+
==Where can I find example scripts?==
  
 
Have a look at the [https://slicer.readthedocs.io/en/latest/developer_guide/script_repository.html Script repository].
 
Have a look at the [https://slicer.readthedocs.io/en/latest/developer_guide/script_repository.html Script repository].
  
== How can I use a visual debugger for step-by-step debugging ==
+
==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 [https://www.jetbrains.com/pycharm/ PyCharm] or [http://pydev.org/ PyDev] by using the ''Python debugger'' module [[Documentation/{{documentation/version}}/Extensions/DebuggingTools|Debugging tools]] extension.
 
 
 
'''See detailed instructions at the [[Documentation/{{documentation/version}}/Extensions/DebuggingTools|Debugging tools extension page]].'''
 
[[File:PyDevRemoteDebugSlicer.png|800px|thumb|center|Visual debugging of Python modules in Slicer]]
 
 
 
===Debugging using Visual Studio===
 
On Windows, [https://github.com/Microsoft/PTVS 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 [[Documentation/{{documentation/version}}/Developers/Tutorials/Debugging_Python_in_Visual_Studio | Debugging Python in Visual Studio]] for details.
 
 
 
===Debugging using remote-pdb===
 
 
 
A command line debugging session with a [https://docs.python.org/3/library/pdb.html pdb] interface can be achieved with [https://github.com/ionelmc/python-remote-pdb 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:
+
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 [https://slicer.readthedocs.io/en/latest/developer_guide/debugging/overview.html#python-debugging here].
  
  telnet 127.0.0.1 1234
+
==Why can't I access my C++ Qt class from python==
  
or socat (has history, readline support):
+
*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:
  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
 
* You cannot access your custom C++ Qt classes from python outside of the scope of your instantiated python class. These will not work:
 
 
<pre>
 
<pre>
 
BarIDRole = slicer.qFooItemDelegate.LastRole + 1
 
BarIDRole = slicer.qFooItemDelegate.LastRole + 1
Line 187: Line 159:
 
</pre>
 
</pre>
  
* [Other reasons go here]
+
*[Other reasons go here]
  
== Can I use factory method like CreateNodeByClass or GetNodesByClass ? ==
+
==Can I use factory method like CreateNodeByClass or GetNodesByClass ?==
  
See [[Documentation/{{documentation/version}}/Developers/Tutorials/MemoryManagement#Factory_methods]]
+
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 ==
+
==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:
 
To get notification about an event emitted by a VTK object you can simply use the AddObserver method, for example:
Line 243: Line 215:
 
For a simplified syntax, see [[#How_to_manage_VTK_object_connections_.3F]]
 
For a simplified syntax, see [[#How_to_manage_VTK_object_connections_.3F]]
  
== How to manage VTK object connections ? ==
+
==How to manage VTK object connections ?==
  
 
The <tt>VTKObservationMixin</tt> is a Python mix-in that allows adding a set of methods to a class by inheritance.  
 
The <tt>VTKObservationMixin</tt> is a Python mix-in that allows adding a set of methods to a class by inheritance.  
 
It includes the following methods defined in [https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/util.py#L995 Base/Python/slicer/util.py]:
 
It includes the following methods defined in [https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/util.py#L995 Base/Python/slicer/util.py]:
* <tt>addObserver</tt>
+
 
* <tt>hasObserver</tt>
+
*<tt>addObserver</tt>
* <tt>observer</tt>
+
*<tt>hasObserver</tt>
* <tt>removeObserver</tt>
+
*<tt>observer</tt>
* <tt>removeObservers</tt>
+
*<tt>removeObserver</tt>
 +
*<tt>removeObservers</tt>
  
 
The following illustrates how to observe the <tt>slicer.vtkMRMLScene.NodeAddedEvent</tt>:
 
The following illustrates how to observe the <tt>slicer.vtkMRMLScene.NodeAddedEvent</tt>:
Line 273: Line 246:
 
For additional examples of usage, see [https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/tests/test_slicer_util_VTKObservationMixin.py test_slicer_util_VTKObservationMixin.py]
 
For additional examples of usage, see [https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/tests/test_slicer_util_VTKObservationMixin.py test_slicer_util_VTKObservationMixin.py]
  
== Slicer crashes if I try to access a non-existing item in an array ==
+
==Slicer crashes if I try to access a non-existing item in an array==
  
 
For example, this code makes Slicer crash:
 
For example, this code makes Slicer crash:
Line 289: Line 262:
  
  
== How to run CLI module from Python? ==
+
==How to run CLI module from Python?==
  
 
See [[Documentation/{{documentation/version}}/Developers/Python_scripting#Running_a_CLI_from_Python|here]].
 
See [[Documentation/{{documentation/version}}/Developers/Python_scripting#Running_a_CLI_from_Python|here]].
  
== How can I run slicer operations from a batch script? ==
+
==How can I run slicer operations from a batch script?==
  
  
Line 328: Line 301:
 
</pre>
 
</pre>
  
== How can I run Slicer on a headless compute node? ==
+
==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.
 
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.
Line 344: Line 317:
 
[https://github.com/pieper/CTK/blob/master/.travis.yml]
 
[https://github.com/pieper/CTK/blob/master/.travis.yml]
  
== How to save user's selection of parameters and nodes in the scene? ==
+
==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.
 
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.
Line 372: Line 345:
 
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''.
 
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 ? ==
+
==How to load a UI file ?==
  
 
See [[Documentation/Nightly/Developers/Tutorials/PythonAndUIFile]]
 
See [[Documentation/Nightly/Developers/Tutorials/PythonAndUIFile]]
  
== How to update progress bar in scripted (Python, or other) CLI modules ==
+
==How to update progress bar in scripted (Python, or other) CLI modules==
  
 
As detailed in the [https://www.slicer.org/wiki/Documentation/Nightly/Developers/SlicerExecutionModel#Showing_Progress_in_an_Application|Slicer 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 <pre>sys.stdout.flush()</pre> in the example Python CLI shown below:
 
As detailed in the [https://www.slicer.org/wiki/Documentation/Nightly/Developers/SlicerExecutionModel#Showing_Progress_in_an_Application|Slicer 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 <pre>sys.stdout.flush()</pre> in the example Python CLI shown below:
Line 399: Line 372:
 
</pre>
 
</pre>
  
== How to display progress bar for CLI module execution in a scripted module ==
+
==How to display progress bar for CLI module execution in a scripted module==
  
 
<pre>
 
<pre>
Line 437: Line 410:
 
See complete example [https://discourse.slicer.org/t/how-to-update-the-progress-bar-from-a-scripted-cli/3789/2?u=lassoan here].
 
See complete example [https://discourse.slicer.org/t/how-to-update-the-progress-bar-from-a-scripted-cli/3789/2?u=lassoan here].
  
== How to run Python script using a non-Slicer Python environment ==
+
==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.
 
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.
Line 461: Line 434:
 
</pre>
 
</pre>
  
== How to configure network proxy ? ==
+
==How to configure network proxy ?==
  
 
When using either <tt>urllib.request</tt> or <tt>requests</tt>, proxy configured by setting <tt>http_proxy</tt> and <tt>https_proxy</tt> environment variables will automatically be used.
 
When using either <tt>urllib.request</tt> or <tt>requests</tt>, proxy configured by setting <tt>http_proxy</tt> and <tt>https_proxy</tt> environment variables will automatically be used.
  
 
For more details:
 
For more details:
* https://docs.python.org/3/library/urllib.request.html#urllib.request.urlopen
+
 
* https://2.python-requests.org/en/master/user/advanced/#proxies
+
*https://docs.python.org/3/library/urllib.request.html#urllib.request.urlopen
 +
*https://2.python-requests.org/en/master/user/advanced/#proxies

Latest revision as of 20:05, 4 February 2022

Home < Documentation < Nightly < Developers < FAQ < Python Scripting


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


Contents

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: