Difference between revisions of "Documentation/Nightly/ScriptRepository"

From Slicer Wiki
Jump to: navigation, search
Tag: 2017 source edit
(48 intermediate revisions by 4 users not shown)
Line 1: Line 1:
<noinclude>{{documentation/versioncheck}}</noinclude>
+
<noinclude>{{documentation/versioncheck}}
 +
</noinclude>
 
__TOC__
 
__TOC__
  
  
=Community-contributed modules=
+
=Community-contributed modules=
  
 
The examples in this section are [[Documentation/{{documentation/version}}/Developers/Modules#Scripted_Modules| Scripted Modules]] that provide a user interface in the module panel along with specialized implementation logic.
 
The examples in this section are [[Documentation/{{documentation/version}}/Developers/Modules#Scripted_Modules| Scripted Modules]] that provide a user interface in the module panel along with specialized implementation logic.
Line 9: Line 10:
 
Usage: save the .py file to a directory, add the directory to the additional module paths in the Slicer application settings (choose in the menu: Edit / Application settings, click Modules, click >> next to Additional module paths, click Add, and choose the .py file's location).
 
Usage: save the .py file to a directory, add the directory to the additional module paths in the Slicer application settings (choose in the menu: Edit / Application settings, click Modules, click >> next to Additional module paths, click Add, and choose the .py file's location).
  
More information about python scripted modules and more usage examples can be found in the [[Documentation/{{documentation/version}}/Developers/Python_scripting | Python scripting]] wiki page.
+
More information about python scripted modules and more usage examples can be found in the[[Documentation/{{documentation/version}}/Developers/Python_scripting | Python scripting]] wiki page.
  
 
==Filters==
 
==Filters==
* [https://raw.github.com/pieper/VolumeMasker/master/VolumeMasker.py VolumeMasker.py]: Update a target volume with the results of setting all input volume voxels to 0 except for those that correspond to a selected label value in an input label map (Used for example in the volume rendering in [https://www.youtube.com/watch?v=dfu2gugHLHs this video).
+
 
 +
*[https://raw.github.com/pieper/VolumeMasker/master/VolumeMasker.py VolumeMasker.py]: Update a target volume with the results of setting all input volume voxels to 0 except for those that correspond to a selected label value in an input label map (Used for example in the volume rendering in [https://www.youtube.com/watch?v=dfu2gugHLHs this video).
  
 
==DICOM==
 
==DICOM==
* [https://gist.github.com/pieper/6186477 dicom header browser] to easily scroll through dicom files using dcmdump.
+
 
* [https://github.com/SlicerRt/SlicerRT/tree/master/BatchProcessing SlicerRT batch processing] to batch convert RT structure sets to labelmap NRRD files.
+
*[https://gist.github.com/pieper/6186477 dicom header browser] to easily scroll through dicom files using dcmdump.
 +
*[https://github.com/SlicerRt/SlicerRT/tree/master/BatchProcessing SlicerRT batch processing] to batch convert RT structure sets to labelmap NRRD files.
  
 
==Informatics==
 
==Informatics==
* [https://gist.github.com/lassoan/bf0954d93cacc8cbe27cd4a3ad503f2f MarkupsInfo.py]: Compute the total length between all the points of a markup list.
+
 
* [https://github.com/lassoan/SlicerLineProfile/blob/master/LineProfile/LineProfile.py LineProfile.py]: Compute intensity profile in a volume along a line.
+
*[https://gist.github.com/lassoan/bf0954d93cacc8cbe27cd4a3ad503f2f MarkupsInfo.py]: Compute the total length between all the points of a markup list.
 +
*[https://github.com/lassoan/SlicerLineProfile/blob/master/LineProfile/LineProfile.py LineProfile.py]: Compute intensity profile in a volume along a line.
  
 
=Community-contributed examples=
 
=Community-contributed examples=
Line 27: Line 31:
  
 
==Capture==
 
==Capture==
* Capture the full Slicer screen and save it into a file
+
 
 +
*Capture the full Slicer screen and save it into a file
 +
 
 
   img = qt.QPixmap.grabWidget(slicer.util.mainWindow()).toImage()
 
   img = qt.QPixmap.grabWidget(slicer.util.mainWindow()).toImage()
 
   img.save('c:/tmp/test.png')
 
   img.save('c:/tmp/test.png')
* Capture all the views save it into a file:
+
 
 +
*Capture all the views save it into a file:
 
<pre>
 
<pre>
 
import ScreenCapture
 
import ScreenCapture
Line 38: Line 45:
 
cap.showViewControllers(True)
 
cap.showViewControllers(True)
 
</pre>
 
</pre>
* Capture a single view:
+
 
 +
*Capture a single view:
 
<pre>
 
<pre>
 
viewNodeID = 'vtkMRMLViewNode1'
 
viewNodeID = 'vtkMRMLViewNode1'
Line 49: Line 57:
 
The ScreenCapture module can also create video animations of rotating views, slice sweeps, etc.
 
The ScreenCapture module can also create video animations of rotating views, slice sweeps, etc.
  
* Capture a slice view sweep into a series of PNG files - for example, Red slice view, 30 images, from position -125.0 to 75.0, into c:/tmp folder, with name image_00001.png, image_00002.png, ...
+
*Capture a slice view sweep into a series of PNG files - for example, Red slice view, 30 images, from position -125.0 to 75.0, into c:/tmp folder, with name image_00001.png, image_00002.png, ...
  
 
<pre>
 
<pre>
Line 56: Line 64:
 
</pre>
 
</pre>
  
* Capture 3D view into PNG file with transparent background
+
*Capture 3D view into PNG file with transparent background
 
<pre>
 
<pre>
 
renderWindow = slicer.app.layoutManager().threeDWidget(0).threeDView().renderWindow()
 
renderWindow = slicer.app.layoutManager().threeDWidget(0).threeDView().renderWindow()
Line 70: Line 78:
  
 
==Launching Slicer==
 
==Launching Slicer==
* How to open an .mrb file with Slicer at the command line?
+
 
 +
*How to open an .mrb file with Slicer at the command line?
 +
 
 
   Slicer.exe --python-code "slicer.util.loadScene( 'f:/2013-08-23-Scene.mrb' )"
 
   Slicer.exe --python-code "slicer.util.loadScene( 'f:/2013-08-23-Scene.mrb' )"
* How to run a script in the Slicer environment in batch mode (without showing any graphical user interface)?
+
 
 +
*How to run a script in the Slicer environment in batch mode (without showing any graphical user interface)?
 +
 
 
   Slicer.exe --python-code "doSomething; doSomethingElse; etc." --testing --no-splash --no-main-window
 
   Slicer.exe --python-code "doSomething; doSomethingElse; etc." --testing --no-splash --no-main-window
  
Line 78: Line 90:
 
When loading a volume from file, it is recommended to set returnNode=True to retrieve the loaded volume node.
 
When loading a volume from file, it is recommended to set returnNode=True to retrieve the loaded volume node.
 
<pre>
 
<pre>
[success, loadedVolumeNode] = slicer.util.loadVolume('c:/Users/abc/Documents/MRHead.nrrd', returnNode=True)
+
loadedVolumeNode = slicer.util.loadVolume('c:/Users/abc/Documents/MRHead.nrrd')
 
</pre>
 
</pre>
  
* Get a MRML node in the scene based on the node name and call methods of that object. For the MRHead sample data:
+
*Get a MRML node in the scene based on the node name and call methods of that object. For the MRHead sample data:
 +
 
 
   vol=slicer.util.getNode('MR*')
 
   vol=slicer.util.getNode('MR*')
 
   vol.GetImageData().GetDimensions()
 
   vol.GetImageData().GetDimensions()
 
  
 
==Show volume rendering automatically when a volume is loaded==
 
==Show volume rendering automatically when a volume is loaded==
Line 116: Line 128:
 
</pre>
 
</pre>
  
== Automatically load volumes that are copied into a folder ==
+
==Automatically load volumes that are copied into a folder==
  
 
This example shows how to implement a simple background task by using a timer. The background task is to check for any new volume files in folder and if there is any then automatically load it.
 
This example shows how to implement a simple background task by using a timer. The background task is to check for any new volume files in folder and if there is any then automatically load it.
Line 149: Line 161:
  
 
==DICOM==
 
==DICOM==
=== How to access top level tags of DICOM images imported into Slicer? For example, to print the first patient's first study's first series' "0020,0032" field:===
+
===How to load DICOM files into the scene from a folder===
 +
 
 +
This code loads all DICOM objects into the scene from a file folder. All the registered plugins are evaluated and the one with the highest confidence will be used to load the data. Files are imported into a temporary DICOM database, so the current Slicer DICOM database is not impacted.
 +
 
 +
  dicomDataDir = "c:/my/folder/with/dicom-files"  # input folder with DICOM files
 +
  loadedNodeIDs = []  # this list will contain the list of all loaded node IDs
 +
 
 +
  from DICOMLib import DICOMUtils
 +
  with DICOMUtils.TemporaryDICOMDatabase() as db:
 +
    DICOMUtils.importDicom(dicomDataDir, db)
 +
    patientUIDs = db.patients()
 +
    for patientUID in patientUIDs:
 +
      loadedNodeIDs.extend(DICOMUtils.loadPatientByUID(patientUID))
 +
 
 +
===How to access top level tags of DICOM images imported into Slicer? For example, to print the first patient's first study's first series' "0020,0032" field:===
 
   db=slicer.dicomDatabase
 
   db=slicer.dicomDatabase
 
   patientList=db.patients()
 
   patientList=db.patients()
Line 159: Line 185:
 
   print(db.fileValue(fileList[0],'0020,0032'))
 
   print(db.fileValue(fileList[0],'0020,0032'))
  
=== How to access DICOM tags nested in a sequence ===
+
===How to access DICOM tags nested in a sequence===
 
   db=slicer.dicomDatabase
 
   db=slicer.dicomDatabase
 
   patientList=db.patients()
 
   patientList=db.patients()
Line 171: Line 197:
 
   ds.CTExposureSequence[0].ExposureModulationType
 
   ds.CTExposureSequence[0].ExposureModulationType
  
=== How to access tag of a volume loaded from DICOM? For example, get the patient position stored in a volume:===
+
===How to access tag of a volume loaded from DICOM? For example, get the patient position stored in a volume:===
 
   volumeName='2: ENT IMRT'
 
   volumeName='2: ENT IMRT'
 
   n=slicer.util.getNode(volumeName)
 
   n=slicer.util.getNode(volumeName)
Line 178: Line 204:
 
   print(slicer.dicomDatabase.fileValue(filename,'0018,5100'))
 
   print(slicer.dicomDatabase.fileValue(filename,'0018,5100'))
  
=== How to access tag of an item in the Subject Hierachy tree? For example, get the content time tag of a structure set:===
+
===How to access tag of an item in the Subject Hierachy tree? For example, get the content time tag of a structure set:===
 
   rtStructName = '3: RTSTRUCT: PROS'
 
   rtStructName = '3: RTSTRUCT: PROS'
 
   rtStructNode = slicer.util.getNode(rtStructName)
 
   rtStructNode = slicer.util.getNode(rtStructName)
Line 187: Line 213:
 
   print(slicer.dicomDatabase.fileValue(filename,'0008,0033'))
 
   print(slicer.dicomDatabase.fileValue(filename,'0008,0033'))
  
=== How to get path and filename of a loaded DICOM volume?===
+
===How to get path and filename of a loaded DICOM volume?===
 
   def pathFromNode(node):
 
   def pathFromNode(node):
 
     storageNode=node.GetStorageNode()
 
     storageNode=node.GetStorageNode()
Line 202: Line 228:
 
   print("DICOM path=%s" % path)
 
   print("DICOM path=%s" % path)
  
=== How can I convert DICOM to NRRD on the command line?===
+
===How can I convert DICOM to NRRD on the command line?===
  
  /Applications/Slicer-4.6.2.app/Contents/MacOS/Slicer --no-main-window --python-code "node=slicer.util.loadVolume('/tmp/series/im0.dcm', returnNode=True)[1]; slicer.util.saveNode(node, '/tmp/output.nrrd'); exit()"
+
  /Applications/Slicer-4.6.2.app/Contents/MacOS/Slicer --no-main-window --python-code "node=slicer.util.loadVolume('/tmp/series/im0.dcm'); slicer.util.saveNode(node, '/tmp/output.nrrd'); exit()"
  
 
The same can be done on windows by using the top level Slicer.exe.  Be sure to use forward slashes in the pathnames within quotes on the command line.
 
The same can be done on windows by using the top level Slicer.exe.  Be sure to use forward slashes in the pathnames within quotes on the command line.
  
=== Export a volume to DICOM file format ===
+
===Export a volume to DICOM file format===
  
 
<pre>
 
<pre>
Line 230: Line 256:
 
</pre>
 
</pre>
  
=== Customize table columns in DICOM browser ===
+
===Customize table columns in DICOM browser===
  
 
<pre>
 
<pre>
Line 251: Line 277:
  
 
==Toolbar functions==
 
==Toolbar functions==
* How to turn on slice intersections in the crosshair menu on the toolbar:
+
 
 +
*How to turn on slice intersections in the crosshair menu on the toolbar:
 
<pre>
 
<pre>
 
viewNodes = slicer.util.getNodesByClass('vtkMRMLSliceCompositeNode')
 
viewNodes = slicer.util.getNodesByClass('vtkMRMLSliceCompositeNode')
Line 261: Line 288:
  
 
==Manipulating objects in the slice viewer==
 
==Manipulating objects in the slice viewer==
* How to define/edit a circular region of interest in a slice viewer?
+
 
 +
*How to define/edit a circular region of interest in a slice viewer?
  
 
Drop two markup points on a slice view and copy-paste the code below into the Python console. After this, as you move the markups you’ll see a circle following the markups.
 
Drop two markup points on a slice view and copy-paste the code below into the Python console. After this, as you move the markups you’ll see a circle following the markups.
Line 294: Line 322:
 
# Call UpdateSphere whenever the fiducials are changed
 
# Call UpdateSphere whenever the fiducials are changed
 
markups.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, UpdateSphere, 2)
 
markups.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, UpdateSphere, 2)
 +
</pre>
 +
 +
==Measure angle between two slice planes==
 +
 +
Measure angle between red and yellow slice nodes. Whenever any of the slice nodes are moved, the updated angle is printed on the console.
 +
 +
<pre>
 +
sliceNodeIds = ['vtkMRMLSliceNodeRed', 'vtkMRMLSliceNodeYellow']
 +
 +
# Print angles between slice nodes
 +
def ShowAngle(unused1=None, unused2=None):
 +
    sliceNormalVector = []
 +
    for sliceNodeId in sliceNodeIds:
 +
        sliceToRAS = slicer.mrmlScene.GetNodeByID(sliceNodeId).GetSliceToRAS()
 +
        sliceNormalVector.append([sliceToRAS.GetElement(0,2), sliceToRAS.GetElement(1,2), sliceToRAS.GetElement(2,2)])
 +
    angleRad = vtk.vtkMath.AngleBetweenVectors(sliceNormalVector[0], sliceNormalVector[1])
 +
    angleDeg = vtk.vtkMath.DegreesFromRadians(angleRad)
 +
    print('Angle between slice planes = {0:0.3f}'.format(angleDeg))
 +
 +
# Observe slice node changes
 +
for sliceNodeId in sliceNodeIds:
 +
    slicer.mrmlScene.GetNodeByID(sliceNodeId).AddObserver(vtk.vtkCommand.ModifiedEvent, ShowAngle)
 +
 +
# Print current angle
 +
ShowAngle()
 
</pre>
 
</pre>
  
Line 335: Line 388:
 
</pre>
 
</pre>
  
== Switching to markup fiducial placement mode ==
 
  
To activate a fiducial placement mode, both interaction mode has to be set and a fiducial node has to be selected:
+
==Set slice position and orientation from a normal vector and position==
 +
 
 +
This code snippet shows how to display a slice view defined by a normal vector and position in an anatomically sensible way: rotating slice view so that "up" direction (or "right" direction) is towards an anatomical axis.
 +
 
 +
<pre>
 +
def setSlicePoseFromSliceNormalAndPosition(sliceNode, sliceNormal, slicePosition, defaultViewUpDirection=None, backupViewRightDirection=None):
 +
    """
 +
    Set slice pose from the provided plane normal and position. View up direction is determined automatically,
 +
    to make view up point towards defaultViewUpDirection.
 +
    :param defaultViewUpDirection Slice view will be spinned in-plane to match point approximately this up direction. Default: patient superior.
 +
    :param backupViewRightDirection Slice view will be spinned in-plane to match point approximately this right direction
 +
        if defaultViewUpDirection is too similar to sliceNormal. Default: patient left.
 +
    """
 +
    # Fix up input directions
 +
    if defaultViewUpDirection is None:
 +
        defaultViewUpDirection = [0,0,1]
 +
    if backupViewRightDirection is None:
 +
        backupViewRightDirection = [-1,0,0]
 +
    if sliceNormal[1]>=0:
 +
        sliceNormalStandardized = sliceNormal
 +
    else:
 +
        sliceNormalStandardized = [-sliceNormal[0], -sliceNormal[1], -sliceNormal[2]]
 +
    # Compute slice axes
 +
    sliceNormalViewUpAngle = vtk.vtkMath.AngleBetweenVectors(sliceNormalStandardized, defaultViewUpDirection)
 +
    angleTooSmallThresholdRad = 0.25 # about 15 degrees
 +
    if sliceNormalViewUpAngle > angleTooSmallThresholdRad and sliceNormalViewUpAngle < vtk.vtkMath.Pi() - angleTooSmallThresholdRad:
 +
        viewUpDirection = defaultViewUpDirection
 +
        sliceAxisY = viewUpDirection
 +
        sliceAxisX = [0, 0, 0]
 +
        vtk.vtkMath.Cross(sliceAxisY, sliceNormalStandardized, sliceAxisX)
 +
    else:
 +
        sliceAxisX = backupViewRightDirection
 +
    # Set slice axes
 +
    sliceNode.SetSliceToRASByNTP(sliceNormalStandardized[0], sliceNormalStandardized[1], sliceNormalStandardized[2],
 +
        sliceAxisX[0], sliceAxisX[1], sliceAxisX[2],
 +
        slicePosition[0], slicePosition[1], slicePosition[2], 0)
 +
 
 +
# Example usage:
 +
sliceNode = getNode('vtkMRMLSliceNodeRed')
 +
transformNode = getNode('Transform_3')
 +
transformMatrix = vtk.vtkMatrix4x4()
 +
transformNode.GetMatrixTransformToParent(transformMatrix)
 +
sliceNormal = [transformMatrix.GetElement(0,2), transformMatrix.GetElement(1,2), transformMatrix.GetElement(2,2)]
 +
slicePosition = [transformMatrix.GetElement(0,3), transformMatrix.GetElement(1,3), transformMatrix.GetElement(2,3)]
 +
setSlicePoseFromSliceNormalAndPosition(sliceNode, sliceNormal, slicePosition)
 +
</pre>
 +
 
 +
==Switching to markup fiducial placement mode==
 +
 
 +
To activate a fiducial placement mode, both interaction mode has to be set and a fiducial node has to be selected:
  
 
<pre>
 
<pre>
Line 370: Line 471:
 
</pre>
 
</pre>
  
== Change markup fiducial display properties ==
+
==Change markup fiducial display properties==
  
 
Display properties are stored in display node(s) associated with the fiducial node.
 
Display properties are stored in display node(s) associated with the fiducial node.
Line 383: Line 484:
 
</pre>
 
</pre>
  
== Get a notification if a markup point position is modified ==
+
==Get a notification if a markup point position is modified==
  
 
Event management of Slicer-4.11 version is still subject to change. The example below shows how point manipulation can be observed now.
 
Event management of Slicer-4.11 version is still subject to change. The example below shows how point manipulation can be observed now.
Line 423: Line 524:
 
</pre>
 
</pre>
  
== Get a notification if a transform is modified ==
+
==Get a notification if a transform is modified==
  
 
<pre>
 
<pre>
Line 435: Line 536:
 
</pre>
 
</pre>
  
== Show a context menu when a markup point is clicked in a slice or 3D view ==
+
==Rotate a node around a specified point==
 +
 
 +
Set up the scene:
 +
 
 +
*Add a markup fiducial node (centerOfRotationMarkupsNode) with a single point to specify center of rotation.
 +
*Add a rotation transform (rotationTransformNode) that will be edited in Transforms module to specify rotation angles.
 +
*Add a transform (finalTransformNode) and apply it (not harden) to those nodes (images, models, etc.) that you want to rotate around the center of rotation point.
 +
 
 +
Then run the script below, go to Transforms module, select rotationTransformNode, and move rotation sliders.
  
 
<pre>
 
<pre>
 +
# This markups fiducial node specifies the center of rotation
 +
centerOfRotationMarkupsNode = getNode('F')
 +
# This transform can be  edited in Transforms module
 +
rotationTransformNode = getNode('LinearTransform_3')
 +
# This transform has to be applied to the image, model, etc.
 +
finalTransformNode = getNode('LinearTransform_4')
  
# Example actions to perform
+
def updateFinalTransform(unusedArg1=None, unusedArg2=None, unusedArg3=None):
 +
    rotationMatrix = vtk.vtkMatrix4x4()
 +
    rotationTransformNode.GetMatrixTransformToParent(rotationMatrix)
 +
    rotationCenterPointCoord = [0.0, 0.0, 0.0]
 +
    centerOfRotationMarkupsNode.GetNthControlPointPositionWorld(0, rotationCenterPointCoord)
 +
    finalTransform = vtk.vtkTransform()
 +
    finalTransform.Translate(rotationCenterPointCoord)
 +
    finalTransform.Concatenate(rotationMatrix)
 +
    finalTransform.Translate(-rotationCenterPointCoord[0], -rotationCenterPointCoord[1], -rotationCenterPointCoord[2])
 +
    finalTransformNode.SetAndObserveMatrixTransformToParent(finalTransform.GetMatrix())
 +
 
 +
# Manual initial update
 +
updateFinalTransform()
 +
 
 +
# Automatic update when point is moved or transform is modified
 +
rotationTransformNodeObserver = rotationTransformNode.AddObserver(slicer.vtkMRMLTransformNode.TransformModifiedEvent, updateFinalTransform)
 +
centerOfRotationMarkupsNodeObserver = centerOfRotationMarkupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, updateFinalTransform)
 +
 
 +
# Execute these lines to stop automatic updates:
 +
# rotationTransformNode.RemoveObserver(rotationTransformNodeObserver)
 +
# centerOfRotationMarkupsNode.RemoveObserver(centerOfRotationMarkupsNodeObserver)
  
def action1():
+
</pre>
  print('Action1 on markup '+str(slicer.clickedMarkupIndex))
 
  
def action2():
+
==Rotate a node around a specified line==
  print('Action2 on markup '+str(slicer.clickedMarkupIndex))
 
  
def action3():
+
Set up the scene:
  print('Action3 on markup '+str(slicer.clickedMarkupIndex))
 
  
# Clicked markup index is saved here to let the action
+
*Add a markup line node (rotationAxisMarkupsNode) with 2 points to specify rotation axis.
# know which markup needs to be manipulated.
+
*Add a rotation transform (rotationTransformNode) that will be edited in Transforms module to specify rotation angle.
slicer.clickedMarkupIndex = -1
+
*Add a transform (finalTransformNode) and apply it (not harden) to those nodes (images, models, etc.) that you want to rotate around the line.
 
 
# Create a simple menu
 
  
menu = qt.QMenu()
+
Then run the script below, go to Transforms module, select rotationTransformNode, and move Edit / Rotation / IS slider.
a1 = qt.QAction("Test", slicer.util.mainWindow())
+
 
a1.connect('triggered()', action1)
+
<pre>
menu.addAction(a1)
+
# This markups fiducial node specifies the center of rotation
a2 = qt.QAction("Action", slicer.util.mainWindow())
+
rotationAxisMarkupsNode = getNode('L')
a2.connect('triggered()', action1)
+
# This transform can be edited in Transforms module (Edit / Rotation / IS slider)
menu.addAction(a2)
+
rotationTransformNode = getNode('LinearTransform_3')
a3 = qt.QAction("Here", slicer.util.mainWindow())
+
# This transform has to be applied to the image, model, etc.
a3.connect('triggered()', action1)
+
finalTransformNode = getNode('LinearTransform_4')
menu.addAction(a3)
 
  
# Add observer to a markup fiducial list
+
def updateFinalTransform(unusedArg1=None, unusedArg2=None, unusedArg3=None):
 +
    import numpy as np
 +
    rotationAxisPoint1_World = np.zeros(3)
 +
    rotationAxisMarkupsNode.GetNthControlPointPositionWorld(0, rotationAxisPoint1_World)
 +
    rotationAxisPoint2_World = np.zeros(3)
 +
    rotationAxisMarkupsNode.GetNthControlPointPositionWorld(1, rotationAxisPoint2_World)
 +
    axisDirectionZ_World = rotationAxisPoint2_World-rotationAxisPoint1_World
 +
    axisDirectionZ_World = axisDirectionZ_World/np.linalg.norm(axisDirectionZ_World)
 +
    # Get transformation between world coordinate system and rotation axis aligne coordinate system
 +
    worldToRotationAxisTransform = vtk.vtkMatrix4x4()
 +
    p=vtk.vtkPlaneSource()
 +
    p.SetNormal(axisDirectionZ_World)
 +
    axisOrigin = np.array(p.GetOrigin())
 +
    axisDirectionX_World = np.array(p.GetPoint1())-axisOrigin
 +
    axisDirectionY_World = np.array(p.GetPoint2())-axisOrigin
 +
    rotationAxisToWorldTransform = np.row_stack((np.column_stack((axisDirectionX_World, axisDirectionY_World, axisDirectionZ_World, rotationAxisPoint1_World)), (0, 0, 0, 1)))
 +
    rotationAxisToWorldTransformMatrix = slicer.util.vtkMatrixFromArray(rotationAxisToWorldTransform)
 +
    worldToRotationAxisTransformMatrix = slicer.util.vtkMatrixFromArray(np.linalg.inv(rotationAxisToWorldTransform))
 +
    # Compute transformation chain
 +
    rotationMatrix = vtk.vtkMatrix4x4()
 +
    rotationTransformNode.GetMatrixTransformToParent(rotationMatrix)
 +
    finalTransform = vtk.vtkTransform()
 +
    finalTransform.Concatenate(rotationAxisToWorldTransformMatrix)
 +
    finalTransform.Concatenate(rotationMatrix)
 +
    finalTransform.Concatenate(worldToRotationAxisTransformMatrix)
 +
    finalTransformNode.SetAndObserveMatrixTransformToParent(finalTransform.GetMatrix())
  
@vtk.calldata_type(vtk.VTK_INT)
+
# Manual initial update
def markupClickedCallback(caller, eventId, callData):
+
updateFinalTransform()
  slicer.clickedMarkupIndex = callData
 
  print('Open menu on markup '+str(slicer.clickedMarkupIndex))
 
  menu.move(qt.QCursor.pos())
 
  menu.show()
 
  
markupsNode = getNode('F')
+
# Automatic update when point is moved or transform is modified
observerTag = markupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointClickedEvent, markupClickedCallback)
+
rotationTransformNodeObserver = rotationTransformNode.AddObserver(slicer.vtkMRMLTransformNode.TransformModifiedEvent, updateFinalTransform)
 +
rotationAxisMarkupsNodeObserver = rotationAxisMarkupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, updateFinalTransform)
  
 +
# Execute these lines to stop automatic updates:
 +
# rotationTransformNode.RemoveObserver(rotationTransformNodeObserver)
 +
# rotationAxisMarkupsNode.RemoveObserver(rotationAxisMarkupsNodeObserver)
 
</pre>
 
</pre>
  
== Write markup positions to JSON file ==
+
==Show a context menu when a markup point is clicked in a slice or 3D view==
  
 
<pre>
 
<pre>
markupNode = getNode('F')
 
outputFileName = 'c:/tmp/test.json'
 
  
# Get markup positions
+
# Example actions to perform
data = []
 
for fidIndex in range(markupNode.GetNumberOfFiducials()):
 
  coords=[0,0,0]
 
  markupNode.GetNthFiducialPosition(fidIndex,coords)
 
  data.append({'label': markupNode.GetNthFiducialLabel(), 'position': coords})
 
  
import json
+
def action1():
with open(outputFileName, 'w') as outfile:
+
   print('Action1 on markup '+str(slicer.clickedMarkupIndex))
   json.dump(data, outfile)
 
</pre>
 
  
== Write annotation ROI to JSON file ==
+
def action2():
 +
  print('Action2 on markup '+str(slicer.clickedMarkupIndex))
  
<pre>
+
def action3():
roiNode = getNode('R')
+
  print('Action3 on markup '+str(slicer.clickedMarkupIndex))
outputFileName = "c:/tmp/test.json"
 
  
# Get annotation ROI data
+
# Clicked markup index is saved here to let the action
center = [0,0,0]
+
# know which markup needs to be manipulated.
radius = [0,0,0]
+
slicer.clickedMarkupIndex = -1
roiNode.GetControlPointWorldCoordinates(0, center)
+
 
roiNode.GetControlPointWorldCoordinates(1, radius)
+
# Create a simple menu
data = {'center': radius, 'radius': radius}
 
  
# Write to json file
+
menu = qt.QMenu()
import json
+
a1 = qt.QAction("Test", slicer.util.mainWindow())
with open(outputFileName, 'w') as outfile:
+
a1.connect('triggered()', action1)
  json.dump(data, outfile)
+
menu.addAction(a1)
</pre>
+
a2 = qt.QAction("Action", slicer.util.mainWindow())
 +
a2.connect('triggered()', action1)
 +
menu.addAction(a2)
 +
a3 = qt.QAction("Here", slicer.util.mainWindow())
 +
a3.connect('triggered()', action1)
 +
menu.addAction(a3)
  
== Show a simple surface mesh as a model node ==
+
# Add observer to a markup fiducial list
  
This example shows how to display a simple surface mesh (a box, created by a VTK source filter) as a model node.
+
@vtk.calldata_type(vtk.VTK_INT)
 +
def markupClickedCallback(caller, eventId, callData):
 +
  slicer.clickedMarkupIndex = callData
 +
  print('Open menu on markup '+str(slicer.clickedMarkupIndex))
 +
  menu.move(qt.QCursor.pos())
 +
  menu.show()
  
<pre>
+
markupsNode = getNode('F')
# Create and set up polydata source
+
observerTag = markupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointClickedEvent, markupClickedCallback)
box = vtk.vtkCubeSource()
 
box.SetXLength(30)
 
box.SetYLength(20)
 
box.SetZLength(15)
 
box.SetCenter(10,20,5)
 
  
# Create a model node that displays output of the source
+
</pre>
boxNode = slicer.modules.models.logic().AddModel(box.GetOutputPort())
 
  
# Adjust display properties
+
==Write markup positions to JSON file==
boxNode.GetDisplayNode().SetColor(1,0,0)
 
boxNode.GetDisplayNode().SetOpacity(0.8)
 
</pre>
 
  
== Add a texture mapped plane to the scene as a model ==
 
Note that model textures are not exposed in the GUI and are not saved in the scene
 
 
<pre>
 
<pre>
# use dummy image data here
+
markupNode = getNode('F')
e = vtk.vtkImageEllipsoidSource()
+
outputFileName = 'c:/tmp/test.json'
  
scene = slicer.mrmlScene
+
# Get markup positions
 +
data = []
 +
for fidIndex in range(markupNode.GetNumberOfFiducials()):
 +
  coords=[0,0,0]
 +
  markupNode.GetNthFiducialPosition(fidIndex,coords)
 +
  data.append({'label': markupNode.GetNthFiducialLabel(), 'position': coords})
  
# Create model node
+
import json
model = slicer.vtkMRMLModelNode()
+
with open(outputFileName, 'w') as outfile:
model.SetScene(scene)
+
  json.dump(data, outfile)
model.SetName(scene.GenerateUniqueName("2DImageModel"))
 
 
 
planeSource = vtk.vtkPlaneSource()
 
model.SetAndObservePolyData(planeSource.GetOutput())
 
 
 
# Create display node
 
modelDisplay = slicer.vtkMRMLModelDisplayNode()
 
modelDisplay.SetColor(1,1,0) # yellow
 
modelDisplay.SetBackfaceCulling(0)
 
modelDisplay.SetScene(scene)
 
scene.AddNode(modelDisplay)
 
model.SetAndObserveDisplayNodeID(modelDisplay.GetID())
 
 
 
# Add to scene
 
modelDisplay.SetAndObserveTextureImageData(e.GetOutput())
 
scene.AddNode(model)
 
 
 
 
 
transform = slicer.vtkMRMLLinearTransformNode()
 
scene.AddNode(transform)
 
model.SetAndObserveTransformNodeID(transform.GetID())
 
 
 
vTransform = vtk.vtkTransform()
 
vTransform.Scale(50,50,50)
 
vTransform.RotateX(30)
 
transform.SetAndObserveMatrixTransformToParent(vTransform.GetMatrix())
 
 
</pre>
 
</pre>
  
== Get scalar values at surface of a model ==
+
==Write annotation ROI to JSON file==
 
 
The following script allows getting selected scalar value at a selected position of a model. Position can be selected by moving the mouse while holding down Shift key.
 
  
 
<pre>
 
<pre>
modelNode = getNode('sphere')
+
roiNode = getNode('R')
modelPointValues = modelNode.GetPolyData().GetPointData().GetArray("Normals")
+
outputFileName = "c:/tmp/test.json"
markupsNode = getNode('F')
 
  
if not markupsNode:
+
# Get annotation ROI data
  markupsNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsFiducialNode","F")
+
center = [0,0,0]
 +
radius = [0,0,0]
 +
roiNode.GetControlPointWorldCoordinates(0, center)
 +
roiNode.GetControlPointWorldCoordinates(1, radius)
 +
data = {'center': radius, 'radius': radius}
  
pointsLocator = vtk.vtkPointLocator() # could try using vtk.vtkStaticPointLocator() if need to optimize
+
# Write to json file
pointsLocator.SetDataSet(modelNode.GetPolyData())
+
import json
pointsLocator.BuildLocator()
+
with open(outputFileName, 'w') as outfile:
 +
  json.dump(data, outfile)
 +
</pre>
  
def onMouseMoved(observer,eventid): 
+
==Show a simple surface mesh as a model node==
  ras=[0,0,0]
 
  crosshairNode.GetCursorPositionRAS(ras)
 
  if markupsNode.GetNumberOfFiducials() == 0:
 
    markupsNode.AddFiducial(*ras)
 
  else:
 
    markupsNode.SetNthFiducialPosition(0,*ras)
 
  closestPointId = pointsLocator.FindClosestPoint(ras)
 
  closestPointValue = modelPointValues.GetTuple(closestPointId)
 
  print("RAS = " + repr(ras) + "    value = " + repr(closestPointValue))
 
  
crosshairNode=slicer.util.getNode('Crosshair')  
+
This example shows how to display a simple surface mesh (a box, created by a VTK source filter) as a model node.
observationId = crosshairNode.AddObserver(slicer.vtkMRMLCrosshairNode.CursorPositionModifiedEvent, onMouseMoved)
 
  
# To stop printing of values run this:
+
<pre>
# crosshairNode.RemoveObserver(observationId)
+
# Create and set up polydata source
 +
box = vtk.vtkCubeSource()
 +
box.SetXLength(30)
 +
box.SetYLength(20)
 +
box.SetZLength(15)
 +
box.SetCenter(10,20,5)
 +
 
 +
# Create a model node that displays output of the source
 +
boxNode = slicer.modules.models.logic().AddModel(box.GetOutputPort())
 +
 
 +
# Adjust display properties
 +
boxNode.GetDisplayNode().SetColor(1,0,0)
 +
boxNode.GetDisplayNode().SetOpacity(0.8)
 
</pre>
 
</pre>
  
== Export entire scene as VRML ==
+
==Measure distance of points from surface==
  
Save all surface meshes displayed in the scene (models, markups, etc). Solid colors and coloring by scalar is preserved. Textures are not supported.
+
This example computes closest distance of points (markups fiducial 'F') from a surface (model node 'mymodel') and writes results into a table.
  
 
<pre>
 
<pre>
exporter = vtk.vtkVRMLExporter()
+
markupsNode = getNode('F')
exporter.SetRenderWindow(slicer.app.layoutManager().threeDWidget(0).threeDView().renderWindow())
+
modelNode = getNode('mymodel')
exporter.SetFileName('C:/tmp/something.wrl')
+
 
exporter.Write()
+
# Transform model polydata to world coordinate system
</pre>
+
if modelNode.GetParentTransformNode():
 +
    transformModelToWorld = vtk.vtkGeneralTransform()
 +
    slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(modelNode.GetParentTransformNode(), None, transformModelToWorld)
 +
    polyTransformToWorld = vtk.vtkTransformPolyDataFilter()
 +
    polyTransformToWorld.SetTransform(transformModelToWorld)
 +
    polyTransformToWorld.SetInputData(modelNode.GetPolyData())
 +
    polyTransformToWorld.Update()
 +
    surface_World = polyTransformToWorld.GetOutput()
 +
else:
 +
    surface_World = modelNode.GetPolyData()
  
== Export model to Blender, including color by scalar ==
+
# Create arrays to store results
 +
indexCol = vtk.vtkIntArray()
 +
indexCol.SetName("Index")
 +
labelCol = vtk.vtkStringArray()
 +
labelCol.SetName("Name")
 +
distanceCol = vtk.vtkDoubleArray()
 +
distanceCol.SetName("Distance")
  
<pre>
+
distanceFilter = vtk.vtkImplicitPolyDataDistance()
modelNode = getNode("Model")
+
distanceFilter.SetInput(surface_World);
plyFilePath = "c:/tmp/model.ply"
+
nOfFiduciallPoints = markupsNode.GetNumberOfFiducials()
 +
for i in range(0, nOfFiduciallPoints):
 +
    point_World = [0,0,0]
 +
    markupsNode.GetNthControlPointPositionWorld(i, point_World)
 +
    closestPointOnSurface_World = [0,0,0]
 +
    closestPointDistance = distanceFilter.EvaluateFunctionAndGetClosestPoint(point_World, closestPointOnSurface_World)
 +
    indexCol.InsertNextValue(i)
 +
    labelCol.InsertNextValue(markupsNode.GetNthControlPointLabel(i))
 +
    distanceCol.InsertNextValue(closestPointDistance)
  
modelDisplayNode = modelNode.GetDisplayNode()
+
# Create a table from result arrays
triangles = vtk.vtkTriangleFilter()
+
resultTableNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLTableNode", "Points from surface distance")
triangles.SetInputConnection(modelDisplayNode.GetOutputPolyDataConnection())
+
resultTableNode.AddColumn(indexCol)
 +
resultTableNode.AddColumn(labelCol)
 +
resultTableNode.AddColumn(distanceCol)
  
plyWriter = vtk.vtkPLYWriter()
+
# Show table in view layout
plyWriter.SetInputConnection(triangles.GetOutputPort())
+
slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutFourUpTableView)
lut = vtk.vtkLookupTable()
+
slicer.app.applicationLogic().GetSelectionNode().SetReferenceActiveTableID(resultTableNode.GetID())
lut.DeepCopy(modelDisplayNode.GetColorNode().GetLookupTable())
+
slicer.app.applicationLogic().PropagateTableSelection()
lut.SetRange(modelDisplayNode.GetScalarRange())
+
</pre>
plyWriter.SetLookupTable(lut)
+
 
plyWriter.SetArrayName(modelDisplayNode.GetActiveScalarName())
+
==Add a texture mapped plane to the scene as a model==
 +
Note that model textures are not exposed in the GUI and are not saved in the scene
 +
<pre>
 +
# Create model node
 +
planeSource = vtk.vtkPlaneSource()
 +
planeSource.SetOrigin(-50.0, -50.0, 0.0)
 +
planeSource.SetPoint1(50.0, -50.0, 0.0)
 +
planeSource.SetPoint2(-50.0, 50.0, 0.0)
 +
model = slicer.modules.models.logic().AddModel(planeSource.GetOutputPort())
 +
 
 +
# Tune display properties
 +
modelDisplay = model.GetDisplayNode()
 +
modelDisplay.SetColor(1,1,0) # yellow
 +
modelDisplay.SetBackfaceCulling(0)
  
plyWriter.SetFileName(plyFilePath)
+
# Add texture (just use image of an ellipsoid)
plyWriter.Write()
+
e = vtk.vtkImageEllipsoidSource()
 +
modelDisplay.SetTextureImageDataConnection(e.GetOutputPort())
 
</pre>
 
</pre>
  
== Export a tract (FiberBundle) to Blender, including color ==
+
==Get scalar values at surface of a model==
<div id="Export_a_fiber_tracts_to_Blender.2C_including_color"></div>
 
Note: an interactive version of this script is now included in the [http://dmri.slicer.org/ SlicerDMRI extension] ([https://github.com/SlicerDMRI/SlicerDMRI/tree/master/Modules/Scripted/TractographyExportPLY module code]).
 
After installing SlicerDMRI, go to ''Modules -> Diffusion -> Import and Export -> Export tractography to PLY (mesh)''.
 
  
The example below shows how to export a tractography "FiberBundleNode" to a PLY file:
+
The following script allows getting selected scalar value at a selected position of a model. Position can be selected by moving the mouse while holding down Shift key.
  
 
<pre>
 
<pre>
lineDisplayNode = getNode("*LineDisplay*")
+
modelNode = getNode('sphere')
plyFilePath = "/tmp/fibers.ply"
+
modelPointValues = modelNode.GetPolyData().GetPointData().GetArray("Normals")
 +
markupsNode = slicer.mrmlScene.GetFirstNodeByName('F')
  
tuber = vtk.vtkTubeFilter()
+
if not markupsNode:
tuber.SetInputData(lineDisplayNode.GetOutputPolyData())
+
  markupsNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsFiducialNode","F")
tuber.Update()
 
tubes = tuber.GetOutputDataObject(0)
 
scalars = tubes.GetPointData().GetArray(0)
 
scalars.SetName("scalars")
 
  
triangles = vtk.vtkTriangleFilter()
+
pointsLocator = vtk.vtkPointLocator() # could try using vtk.vtkStaticPointLocator() if need to optimize
triangles.SetInputData(tubes)
+
pointsLocator.SetDataSet(modelNode.GetPolyData())
triangles.Update()
+
pointsLocator.BuildLocator()
  
colorNode = lineDisplayNode.GetColorNode()
+
def onMouseMoved(observer,eventid): 
lookupTable = vtk.vtkLookupTable()
+
  ras=[0,0,0]
lookupTable.DeepCopy(colorNode.GetLookupTable())
+
  crosshairNode.GetCursorPositionRAS(ras)
lookupTable.SetTableRange(0,1)
+
  if markupsNode.GetNumberOfFiducials() == 0:
 +
    markupsNode.AddFiducial(*ras)
 +
  else:
 +
    markupsNode.SetNthFiducialPosition(0,*ras)
 +
  closestPointId = pointsLocator.FindClosestPoint(ras)
 +
  closestPointValue = modelPointValues.GetTuple(closestPointId)
 +
  print("RAS = " + repr(ras) + "    value = " + repr(closestPointValue))
  
plyWriter = vtk.vtkPLYWriter()
+
crosshairNode=slicer.util.getNode('Crosshair')  
plyWriter.SetInputData(triangles.GetOutput())
+
observationId = crosshairNode.AddObserver(slicer.vtkMRMLCrosshairNode.CursorPositionModifiedEvent, onMouseMoved)
plyWriter.SetLookupTable(lookupTable)
 
plyWriter.SetArrayName("scalars")
 
  
plyWriter.SetFileName(plyFilePath)
+
# To stop printing of values run this:
plyWriter.Write()
+
# crosshairNode.RemoveObserver(observationId)
 
</pre>
 
</pre>
  
== Iterate over tract (FiberBundle) streamline points ==
+
==Select cells of a model using markups fiducial points==
  
This example shows how to access the points in each line of a FiberBundle as a numpy array (view).
+
The following script selects cells of a model node that are closest to positions of markups fiducial points.
  
 
<pre>
 
<pre>
from vtk.util.numpy_support import vtk_to_numpy
+
# Get input nodes
 +
modelNode = slicer.util.getNode('Segment_1') # select cells in this model
 +
markupsNode = slicer.util.getNode('F') # points will be selected at positions specified by this markups fiducial node
  
fb = getNode("FiberBundle_F") # <- fill in node ID here
+
# Create scalar array that will store selection state
 +
cellScalars = modelNode.GetMesh().GetCellData()
 +
selectionArray = cellScalars.GetArray('selection')
 +
if not selectionArray:
 +
    selectionArray = vtk.vtkIntArray()
 +
    selectionArray.SetName('selection')
 +
    selectionArray.SetNumberOfValues(modelNode.GetMesh().GetNumberOfCells())
 +
    selectionArray.Fill(0)
 +
    cellScalars.AddArray(selectionArray)
  
# get point data as 1d array
+
# Set up coloring by selection array
points = slicer.util.arrayFromModelPoints(fb)
+
modelNode.GetDisplayNode().SetActiveScalar("selection", vtk.vtkAssignAttribute.CELL_DATA)
 +
modelNode.GetDisplayNode().SetAndObserveColorNodeID("vtkMRMLColorTableNodeWarm1")
 +
modelNode.GetDisplayNode().SetScalarVisibility(True)
  
# get line cell ids as 1d array
+
# Initialize cell locator
line_ids = vtk_to_numpy(fb.GetPolyData().GetLines().GetData())
+
cell = vtk.vtkCellLocator()
 +
cell.SetDataSet(modelNode.GetMesh())
 +
cell.BuildLocator()
  
# VTK cell ids are stored as
+
def onPointsModified(observer=None, eventid=None):
#  [ N0 c0_id0 ... c0_id0
+
    global markupsNode, selectionArray
#     N1 c1_id0 ... c1_idN1 ]
+
    selectionArray.Fill(0) # set all cells to non-selected by default
# so we need to
+
    markupPoints = slicer.util.arrayFromMarkupsControlPoints(markupsNode)
# - read point count for each line (cell)
+
     closestPoint = [0.0, 0.0, 0.0]
# - grab the ids in that range from `line_ids` array defined above
+
    cellObj = vtk.vtkGenericCell()
# - index the `points` array by those ids
+
    cellId = vtk.mutable(0)
cur_idx = 1
+
    subId = vtk.mutable(0)
for _ in range(pd.GetLines().GetNumberOfCells()):
+
    dist2 = vtk.mutable(0.0)
    # - read point count for this line (cell)
+
    for markupPoint in markupPoints:
    count = lines[cur_idx - 1]
+
        cell.FindClosestPoint(markupPoint, closestPoint, cellObj, cellId, subId, dist2)
 +
        closestCell = cellId.get()
 +
        if closestCell >=0:
 +
            selectionArray.SetValue(closestCell, 100) # set selected cell's scalar value to non-zero
 +
    selectionArray.Modified()
  
    # - grab the ids in that range from `lines`
+
# Initial update
    index_array = line_ids[ cur_idx : cur_idx + count]
+
onPointsModified()
    # update to the next range
+
# Automatic update each time when a markup point is modified
    cur_idx += count + 1
+
markupsNodeObserverTag = markupsNode.AddObserver(slicer.vtkMRMLMarkupsFiducialNode.PointModifiedEvent, onPointsModified)
  
    # - index the point array by those ids
+
# To stop updating selection, run this:
    line_points = points[index_array]
+
# markupsNode.RemoveObserver(markupsNodeObserverTag)
 +
</pre>
  
    # do work here
+
==Export entire scene as VRML==
</pre>
 
  
== Clone a node ==
+
Save all surface meshes displayed in the scene (models, markups, etc). Solid colors and coloring by scalar is preserved. Textures are not supported.
 
 
This example shows how to make a copy of any node that appears in Subject Hierarchy (in Data module).
 
  
 
<pre>
 
<pre>
# Get a node from SampleData that we will clone
+
exporter = vtk.vtkVRMLExporter()
import SampleData
+
exporter.SetRenderWindow(slicer.app.layoutManager().threeDWidget(0).threeDView().renderWindow())
nodeToClone = SampleData.SampleDataLogic().downloadMRHead()
+
exporter.SetFileName('C:/tmp/something.wrl')
 +
exporter.Write()
 +
</pre>
  
# Clone the node
+
==Export model to Blender, including color by scalar==
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)
 
itemIDToClone = shNode.GetItemByDataNode(nodeToClone)
 
clonedItemID = slicer.modules.subjecthierarchy.logic().CloneSubjectHierarchyItem(shNode, itemIDToClone)
 
clonedNode = shNode.GetItemDataNode(clonedItemID)
 
</pre>
 
  
== Clone a volume ==
 
This example shows how to clone the MRHead sample volume, including its pixel data and display settings.
 
 
<pre>
 
<pre>
sourceVolumeNode = slicer.util.getNode('MRHead')
+
modelNode = getNode("Model")
volumesLogic = slicer.modules.volumes.logic()
+
plyFilePath = "c:/tmp/model.ply"
clonedVolumeNode = volumesLogic.CloneVolume(slicer.mrmlScene, sourceVolumeNode, 'Cloned volume')
 
</pre>
 
  
== Create a new volume ==
+
modelDisplayNode = modelNode.GetDisplayNode()
This example shows how to create a new empty volume.
+
triangles = vtk.vtkTriangleFilter()
<pre>
+
triangles.SetInputConnection(modelDisplayNode.GetOutputPolyDataConnection())
nodeName = "MyNewVolume"
+
 
imageSize = [512, 512, 512]
+
plyWriter = vtk.vtkPLYWriter()
voxelType=vtk.VTK_UNSIGNED_CHAR
+
plyWriter.SetInputConnection(triangles.GetOutputPort())
imageOrigin = [0.0, 0.0, 0.0]
+
lut = vtk.vtkLookupTable()
imageSpacing = [1.0, 1.0, 1.0]
+
lut.DeepCopy(modelDisplayNode.GetColorNode().GetLookupTable())
imageDirections = [[1,0,0], [0,1,0], [0,0,1]]
+
lut.SetRange(modelDisplayNode.GetScalarRange())
fillVoxelValue = 0
+
plyWriter.SetLookupTable(lut)
 +
plyWriter.SetArrayName(modelDisplayNode.GetActiveScalarName())
  
# Create an empty image volume, filled with fillVoxelValue
+
plyWriter.SetFileName(plyFilePath)
imageData = vtk.vtkImageData()
+
plyWriter.Write()
imageData.SetDimensions(imageSize)
 
imageData.AllocateScalars(voxelType, 1)
 
thresholder = vtk.vtkImageThreshold()
 
thresholder.SetInputData(imageData)
 
thresholder.SetInValue(fillVoxelValue)
 
thresholder.SetOutValue(fillVoxelValue)
 
thresholder.Update()
 
# Create volume node
 
volumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode", nodeName)
 
volumeNode.SetOrigin(imageOrigin)
 
volumeNode.SetSpacing(imageSpacing)
 
volumeNode.SetIJKToRASDirections(imageDirections)
 
volumeNode.SetAndObserveImageData(thresholder.GetOutput())
 
volumeNode.CreateDefaultDisplayNodes()
 
volumeNode.CreateDefaultStorageNode()
 
 
</pre>
 
</pre>
  
== Modify voxels in a volume ==
+
==Export a tract (FiberBundle) to Blender, including color==
 +
<div id="Export_a_fiber_tracts_to_Blender.2C_including_color"></div>
 +
Note: an interactive version of this script is now included in the [http://dmri.slicer.org/ SlicerDMRI extension] ([https://github.com/SlicerDMRI/SlicerDMRI/tree/master/Modules/Scripted/TractographyExportPLY module code]).
 +
After installing SlicerDMRI, go to ''Modules -> Diffusion -> Import and Export -> Export tractography to PLY (mesh)''.
  
Typically the fastest and simplest way of modifying voxels is by using numpy operators. Voxels can be retrieved in a numpy array using the `array` method and modified using standard numpy methods. For example, threshold a volume:
+
The example below shows how to export a tractography "FiberBundleNode" to a PLY file:
  
 
<pre>
 
<pre>
nodeName = 'MRHead'
+
lineDisplayNode = getNode("*LineDisplay*")
thresholdValue = 100
+
plyFilePath = "/tmp/fibers.ply"
voxelArray = array(nodeName) # get voxels as numpy array
+
 
voxelArray[voxelArray < thresholdValue] = 0 # modify voxel values
+
tuber = vtk.vtkTubeFilter()
getNode(nodeName).Modified() # at the end of all processing, notify Slicer that the image modification is completed
+
tuber.SetInputData(lineDisplayNode.GetOutputPolyData())
</pre>
+
tuber.Update()
 +
tubes = tuber.GetOutputDataObject(0)
 +
scalars = tubes.GetPointData().GetArray(0)
 +
scalars.SetName("scalars")
  
This example shows how to change voxels values of the MRHead sample volume.
+
triangles = vtk.vtkTriangleFilter()
The values will be computed by function f(r,a,s,) = (r-10)*(r-10)+(a+15)*(a+15)+s*s.
+
triangles.SetInputData(tubes)
<pre>
+
triangles.Update()
volumeNode=slicer.util.getNode('MRHead')
+
 
ijkToRas = vtk.vtkMatrix4x4()
+
colorNode = lineDisplayNode.GetColorNode()
volumeNode.GetIJKToRASMatrix(ijkToRas)
+
lookupTable = vtk.vtkLookupTable()
imageData=volumeNode.GetImageData()
+
lookupTable.DeepCopy(colorNode.GetLookupTable())
extent = imageData.GetExtent()
+
lookupTable.SetTableRange(0,1)
for k in range(extent[4], extent[5]+1):
+
 
  for j in range(extent[2], extent[3]+1):
+
plyWriter = vtk.vtkPLYWriter()
    for i in range(extent[0], extent[1]+1):
+
plyWriter.SetInputData(triangles.GetOutput())
      position_Ijk=[i, j, k, 1]
+
plyWriter.SetLookupTable(lookupTable)
      position_Ras=ijkToRas.MultiplyPoint(position_Ijk)
+
plyWriter.SetArrayName("scalars")
      r=position_Ras[0]
+
 
      a=position_Ras[1]
+
plyWriter.SetFileName(plyFilePath)
      s=position_Ras[2]     
+
plyWriter.Write()
      functionValue=(r-10)*(r-10)+(a+15)*(a+15)+s*s
 
      imageData.SetScalarComponentFromDouble(i,j,k,0,functionValue)
 
imageData.SetScalarComponentFromFloat(distortionVectorPosition_Ijk[0], distortionVectorPosition_Ijk[1], distortionVectorPosition_Ijk[2], 0, fillValue)
 
imageData.Modified()
 
 
</pre>
 
</pre>
  
== Get volume voxel coordinates from markup fiducial RAS coordinates ==
+
==Iterate over tract (FiberBundle) streamline points==
  
This example shows how to get voxel coordinate of a volume corresponding to a markup fiducial point position.
+
This example shows how to access the points in each line of a FiberBundle as a numpy array (view).
  
 
<pre>
 
<pre>
# Inputs
+
from vtk.util.numpy_support import vtk_to_numpy
volumeNode = getNode('MRHead')
+
 
markupsNode = getNode('F')
+
fb = getNode("FiberBundle_F") # <- fill in node ID here
markupsIndex = 0
 
  
# Get point coordinate in RAS
+
# get point data as 1d array
point_Ras = [0, 0, 0, 1]
+
points = slicer.util.arrayFromModelPoints(fb)
markupsNode.GetNthFiducialWorldCoordinates(markupsIndex, point_Ras)
 
  
# If volume node is transformed, apply that transform to get volume's RAS coordinates
+
# get line cell ids as 1d array
transformRasToVolumeRas = vtk.vtkGeneralTransform()
+
line_ids = vtk_to_numpy(fb.GetPolyData().GetLines().GetData())
slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(None, volumeNode.GetParentTransformNode(), transformRasToVolumeRas)
 
point_VolumeRas = transformRasToVolumeRas.TransformPoint(point_Ras[0:3])
 
  
# Get voxel coordinates from physical coordinates
+
# VTK cell ids are stored as
volumeRasToIjk = vtk.vtkMatrix4x4()
+
#  [ N0 c0_id0 ... c0_id0
volumeNode.GetRASToIJKMatrix(volumeRasToIjk)
+
#    N1 c1_id0 ... c1_idN1 ]
point_Ijk = [0, 0, 0, 1]
+
# so we need to
volumeRasToIjk.MultiplyPoint(np.append(point_VolumeRas,1.0), point_Ijk)
+
# - read point count for each line (cell)
point_Ijk = [ int(round(c)) for c in point_Ijk[0:3] ]
+
# - grab the ids in that range from `line_ids` array defined above
 
+
# - index the `points` array by those ids
# Print output
+
cur_idx = 1
print(point_Ijk)
+
for _ in range(pd.GetLines().GetNumberOfCells()):
 +
    # - read point count for this line (cell)
 +
    count = lines[cur_idx - 1]
 +
 
 +
    # - grab the ids in that range from `lines`
 +
    index_array = line_ids[ cur_idx : cur_idx + count]
 +
    # update to the next range
 +
    cur_idx += count + 1
 +
 
 +
    # - index the point array by those ids
 +
    line_points = points[index_array]
 +
 
 +
    # do work here
 
</pre>
 
</pre>
  
== Get markup fiducial RAS coordinates from volume voxel coordinates ==
+
==Clone a node==
  
This example shows how to get position of maximum intensity voxel of a volume (determined by numpy, in IJK coordinates) in RAS coordinates so that it can be marked with a markup fiducial.
+
This example shows how to make a copy of any node that appears in Subject Hierarchy (in Data module).
  
 
<pre>
 
<pre>
# Inputs
+
# Get a node from SampleData that we will clone
volumeNode = getNode('MRHead')
+
import SampleData
markupsNode = getNode('F')
+
nodeToClone = SampleData.SampleDataLogic().downloadMRHead()
  
# Get voxel position in IJK coordinate system
+
# Clone the node
import numpy as np
+
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)
volumeArray = slicer.util.arrayFromVolume(volumeNode)
+
itemIDToClone = shNode.GetItemByDataNode(nodeToClone)
# Get position of highest voxel value
+
clonedItemID = slicer.modules.subjecthierarchy.logic().CloneSubjectHierarchyItem(shNode, itemIDToClone)
point_Kji = np.where(volumeArray == volumeArray.max())
+
clonedNode = shNode.GetItemDataNode(clonedItemID)
point_Ijk = [point_Kji[2][0], point_Kji[1][0], point_Kji[0][0]]
+
</pre>
  
# Get physical coordinates from voxel coordinates
+
==Clone a volume==
volumeIjkToRas = vtk.vtkMatrix4x4()
+
This example shows how to clone the MRHead sample volume, including its pixel data and display settings.
volumeNode.GetIJKToRASMatrix(volumeIjkToRas)
+
<pre>
point_VolumeRas = [0, 0, 0, 1]
+
sourceVolumeNode = slicer.util.getNode('MRHead')
volumeIjkToRas.MultiplyPoint(np.append(point_Ijk,1.0), point_VolumeRas)
+
volumesLogic = slicer.modules.volumes.logic()
 
+
clonedVolumeNode = volumesLogic.CloneVolume(slicer.mrmlScene, sourceVolumeNode, 'Cloned volume')
# If volume node is transformed, apply that transform to get volume's RAS coordinates
 
transformVolumeRasToRas = vtk.vtkGeneralTransform()
 
slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(volumeNode.GetParentTransformNode(), None, transformVolumeRasToRas)
 
point_Ras = transformVolumeRasToRas.TransformPoint(point_VolumeRas[0:3])
 
 
 
# Add a markup at the computed position and print its coordinates
 
markupsNode.AddFiducial(point_Ras[0], point_Ras[1], point_Ras[2], "max")
 
print(point_Ras)
 
 
</pre>
 
</pre>
  
== Get the values of all voxels for a label value  ==
+
==Create a new volume==
 
+
This example shows how to create a new empty volume.
If you have a background image called ‘Volume’ and a mask called ‘Volume-label’ created with the Editor you could do something like this:
 
 
 
 
<pre>
 
<pre>
 
+
nodeName = "MyNewVolume"
import numpy
+
imageSize = [512, 512, 512]
volume = array(‘Volume’)
+
voxelType=vtk.VTK_UNSIGNED_CHAR
label = array(‘Volume-label’)
+
imageOrigin = [0.0, 0.0, 0.0]
points  = numpy.where( label == 1 ) # or use another label number depending on what you segmented
+
imageSpacing = [1.0, 1.0, 1.0]
values  = volume[points] # this will be a list of the label values
+
imageDirections = [[1,0,0], [0,1,0], [0,0,1]]
values.mean() # should match the mean value of LabelStatistics calculation as a double-check
+
fillVoxelValue = 0
numpy.savetxt(‘values.txt’, values)
+
 
 +
# Create an empty image volume, filled with fillVoxelValue
 +
imageData = vtk.vtkImageData()
 +
imageData.SetDimensions(imageSize)
 +
imageData.AllocateScalars(voxelType, 1)
 +
imageData.GetPointData().GetScalars().Fill(fillVoxelValue)
 +
# Create volume node
 +
volumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode", nodeName)
 +
volumeNode.SetOrigin(imageOrigin)
 +
volumeNode.SetSpacing(imageSpacing)
 +
volumeNode.SetIJKToRASDirections(imageDirections)
 +
volumeNode.SetAndObserveImageData(imageData)
 +
volumeNode.CreateDefaultDisplayNodes()
 +
volumeNode.CreateDefaultStorageNode()
 
</pre>
 
</pre>
  
== Access values in a DTI tensor volume ==
+
==Get value of a volume at specific voxel coordinates==
This example shows how to access individual tensors at the voxel level.
 
  
First load your DWI volume and estimate tensors to produce a DTI volume called ‘Output DTI Volume’
+
This example shows how to get voxel value of "volumeNode" at "ijk" volume voxel coordinates.
  
Then open the python window: View->Python interactor
+
<pre>
 +
volumeNode = slicer.util.getNode('MRHead')
 +
ijk = [20,40,30]  # volume voxel coordinates
  
Use this command to access tensors through numpy:
+
voxels = slicer.util.arrayFromVolume(volumeNode)  # get voxels as a numpy array
 +
voxelValue = voxels[ijk[2], ijk[1], ijk[0]]  # note that numpy array index order is kji (not ijk)
 +
</pre>
 +
 
 +
==Modify voxels in a volume==
 +
 
 +
Typically the fastest and simplest way of modifying voxels is by using numpy operators. Voxels can be retrieved in a numpy array using the `array` method and modified using standard numpy methods. For example, threshold a volume:
  
 
<pre>
 
<pre>
tensors = array('Output DTI Volume')
+
nodeName = 'MRHead'
 +
thresholdValue = 100
 +
voxelArray = array(nodeName) # get voxels as numpy array
 +
voxelArray[voxelArray < thresholdValue] = 0 # modify voxel values
 +
getNode(nodeName).Modified() # at the end of all processing, notify Slicer that the image modification is completed
 
</pre>
 
</pre>
  
Type the following code into the Python window to access all tensor components using vtk commands:
+
This example shows how to change voxels values of the MRHead sample volume.
 
+
The values will be computed by function f(r,a,s,) = (r-10)*(r-10)+(a+15)*(a+15)+s*s.
 
<pre>
 
<pre>
volumeNode=slicer.util.getNode('Output DTI Volume')
+
volumeNode=slicer.util.getNode('MRHead')
 +
ijkToRas = vtk.vtkMatrix4x4()
 +
volumeNode.GetIJKToRASMatrix(ijkToRas)
 
imageData=volumeNode.GetImageData()
 
imageData=volumeNode.GetImageData()
tensors = imageData.GetPointData().GetTensors()
 
 
extent = imageData.GetExtent()
 
extent = imageData.GetExtent()
idx = 0
 
 
for k in range(extent[4], extent[5]+1):
 
for k in range(extent[4], extent[5]+1):
 
   for j in range(extent[2], extent[3]+1):
 
   for j in range(extent[2], extent[3]+1):
 
     for i in range(extent[0], extent[1]+1):
 
     for i in range(extent[0], extent[1]+1):
       tensors.GetTuple9(idx)
+
       position_Ijk=[i, j, k, 1]
       idx += 1
+
      position_Ras=ijkToRas.MultiplyPoint(position_Ijk)
</pre>
+
       r=position_Ras[0]
 
+
      a=position_Ras[1]
== Change window/level (brightness/contrast) or colormap of a volume ==
+
      s=position_Ras[2]     
This example shows how to change window/level of the MRHead sample volume.
+
      functionValue=(r-10)*(r-10)+(a+15)*(a+15)+s*s
<pre>
+
      imageData.SetScalarComponentFromDouble(i,j,k,0,functionValue)
volumeNode = getNode('MRHead')
+
imageData.Modified()
displayNode = volumeNode.GetDisplayNode()
 
displayNode.AutoWindowLevelOff()
 
displayNode.SetWindow(50)
 
displayNode.SetLevel(100)
 
 
</pre>
 
</pre>
  
Change color mapping from grayscale to rainbow:
+
==Get volume voxel coordinates from markup fiducial RAS coordinates==
<pre>
 
displayNode.SetAndObserveColorNodeID('vtkMRMLColorTableNodeRainbow')
 
</pre>
 
  
== Make mouse left-click and drag on the image adjust window/level ==
+
This example shows how to get voxel coordinate of a volume corresponding to a markup fiducial point position.
  
In older Slicer versions, by default, left-click and drag in a slice view adjusted window/level of the displayed image. Window/level adjustment is now a new mouse mode that can be activated by clicking on its toolbar button or running this code:
+
<pre>
 +
# Inputs
 +
volumeNode = getNode('MRHead')
 +
markupsNode = getNode('F')
 +
markupsIndex = 0
  
<pre>
+
# Get point coordinate in RAS
slicer.app.applicationLogic().GetInteractionNode().SetCurrentInteractionMode(slicer.vtkMRMLInteractionNode.AdjustWindowLevel)
+
point_Ras = [0, 0, 0, 1]
</pre>
+
markupsNode.GetNthFiducialWorldCoordinates(markupsIndex, point_Ras)
  
== Create custom color table ==
+
# If volume node is transformed, apply that transform to get volume's RAS coordinates
This example shows how to create a new color table, for example with inverted color range from the default Ocean color table.
+
transformRasToVolumeRas = vtk.vtkGeneralTransform()
<pre>
+
slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(None, volumeNode.GetParentTransformNode(), transformRasToVolumeRas)
invertedocean = slicer.vtkMRMLColorTableNode()
+
point_VolumeRas = transformRasToVolumeRas.TransformPoint(point_Ras[0:3])
invertedocean.SetTypeToUser()
 
invertedocean.SetNumberOfColors(256)
 
invertedocean.SetName("InvertedOcean")
 
  
for i in range(0,255):
+
# Get voxel coordinates from physical coordinates
    invertedocean.SetColor(i, 0.0, 1 - (i+1e-16)/255.0, 1.0, 1.0)
+
volumeRasToIjk = vtk.vtkMatrix4x4()
 +
volumeNode.GetRASToIJKMatrix(volumeRasToIjk)
 +
point_Ijk = [0, 0, 0, 1]
 +
volumeRasToIjk.MultiplyPoint(np.append(point_VolumeRas,1.0), point_Ijk)
 +
point_Ijk = [ int(round(c)) for c in point_Ijk[0:3] ]
  
slicer.mrmlScene.AddNode(invertedocean)
+
# Print output
 +
print(point_Ijk)
 
</pre>
 
</pre>
  
== Manipulate a Slice View ==
+
==Get markup fiducial RAS coordinates from volume voxel coordinates==
  
=== Change slice offset ===
+
This example shows how to get position of maximum intensity voxel of a volume (determined by numpy, in IJK coordinates) in RAS coordinates so that it can be marked with a markup fiducial.
  
Equivalent to moving the slider in slice view controller.
+
<pre>
 +
# Inputs
 +
volumeNode = getNode('MRHead')
 +
markupsNode = getNode('F')
  
<pre>
+
# Get voxel position in IJK coordinate system
layoutManager = slicer.app.layoutManager()
+
import numpy as np
red = layoutManager.sliceWidget('Red')
+
volumeArray = slicer.util.arrayFromVolume(volumeNode)
redLogic = red.sliceLogic()
+
# Get position of highest voxel value
# Print current slice offset position
+
point_Kji = np.where(volumeArray == volumeArray.max())
print(redLogic.GetSliceOffset())
+
point_Ijk = [point_Kji[2][0], point_Kji[1][0], point_Kji[0][0]]
# Change slice position
+
 
redLogic.SetSliceOffset(20)
+
# Get physical coordinates from voxel coordinates
 +
volumeIjkToRas = vtk.vtkMatrix4x4()
 +
volumeNode.GetIJKToRASMatrix(volumeIjkToRas)
 +
point_VolumeRas = [0, 0, 0, 1]
 +
volumeIjkToRas.MultiplyPoint(np.append(point_Ijk,1.0), point_VolumeRas)
 +
 
 +
# If volume node is transformed, apply that transform to get volume's RAS coordinates
 +
transformVolumeRasToRas = vtk.vtkGeneralTransform()
 +
slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(volumeNode.GetParentTransformNode(), None, transformVolumeRasToRas)
 +
point_Ras = transformVolumeRasToRas.TransformPoint(point_VolumeRas[0:3])
 +
 
 +
# Add a markup at the computed position and print its coordinates
 +
markupsNode.AddFiducial(point_Ras[0], point_Ras[1], point_Ras[2], "max")
 +
print(point_Ras)
 
</pre>
 
</pre>
  
=== Change slice orientation ===
+
==Get the values of all voxels for a label value==
  
Get 'Red' slice node and rotate around X and Y axes.
+
If you have a background image called ‘Volume’ and a mask called ‘Volume-label’ created with the Editor you could do something like this:
  
 
<pre>
 
<pre>
sliceNode = slicer.app.layoutManager().sliceWidget('Red').mrmlSliceNode()
+
 
sliceToRas = sliceNode.GetSliceToRAS()
+
import numpy
transform=vtk.vtkTransform()
+
volume = array(‘Volume’)
transform.SetMatrix(SliceToRAS)
+
label = array(‘Volume-label’)
transform.RotateX(20)
+
points  = numpy.where( label == 1 ) # or use another label number depending on what you segmented
transform.RotateY(15)
+
values  = volume[points] # this will be a list of the label values
sliceToRas.DeepCopy(transform.GetMatrix())
+
values.mean() # should match the mean value of LabelStatistics calculation as a double-check
sliceNode.UpdateMatrices()
+
numpy.savetxt(‘values.txt’, values)
 
</pre>
 
</pre>
  
=== Show slice views in 3D window ===
+
==Access values in a DTI tensor volume==
 +
This example shows how to access individual tensors at the voxel level.
  
Equivalent to clicking 'eye' icon in the slice view controller.
+
First load your DWI volume and estimate tensors to produce a DTI volume called ‘Output DTI Volume’
  
<pre>
+
Then open the python window: View->Python interactor
layoutManager = slicer.app.layoutManager()
 
for sliceViewName in layoutManager.sliceViewNames():
 
  controller = layoutManager.sliceWidget(sliceViewName).sliceController()
 
  controller.setSliceVisible(True)
 
</pre>
 
  
=== Reset field of view to show background volume maximized ===
+
Use this command to access tensors through numpy:
 
 
Equivalent to click small rectangle button ("Adjust the slice viewer's field of view...") in the slice view controller.
 
  
 
<pre>
 
<pre>
slicer.util.resetSliceViews()
+
tensors = array('Output DTI Volume')
 
</pre>
 
</pre>
  
=== Rotate slice views to volume plane ===
+
Type the following code into the Python window to access all tensor components using vtk commands:
 
 
Aligns slice views to volume axes, shows original image acquisition planes in slice views.
 
  
 
<pre>
 
<pre>
volumeNode = slicer.util.getNode('MRHead')
+
volumeNode=slicer.util.getNode('Output DTI Volume')
layoutManager = slicer.app.layoutManager()
+
imageData=volumeNode.GetImageData()
for sliceViewName in layoutManager.sliceViewNames():
+
tensors = imageData.GetPointData().GetTensors()
   layoutManager.sliceWidget(sliceViewName).mrmlSliceNode().RotateToVolumePlane(volumeNode)
+
extent = imageData.GetExtent()
 +
idx = 0
 +
for k in range(extent[4], extent[5]+1):
 +
   for j in range(extent[2], extent[3]+1):
 +
    for i in range(extent[0], extent[1]+1):
 +
      tensors.GetTuple9(idx)
 +
      idx += 1
 
</pre>
 
</pre>
  
=== Iterate over current visible slice views, and set foreground and background images ===
+
==Change window/level (brightness/contrast) or colormap of a volume==
 +
This example shows how to change window/level of the MRHead sample volume.
 +
<pre>
 +
volumeNode = getNode('MRHead')
 +
displayNode = volumeNode.GetDisplayNode()
 +
displayNode.AutoWindowLevelOff()
 +
displayNode.SetWindow(50)
 +
displayNode.SetLevel(100)
 +
</pre>
  
 +
Change color mapping from grayscale to rainbow:
 
<pre>
 
<pre>
slicer.util.setSliceViewerLayers(background=mrVolume, foreground=ctVolume)
+
displayNode.SetAndObserveColorNodeID('vtkMRMLColorTableNodeRainbow')
 
</pre>
 
</pre>
  
Internally, this method performs something like this:
+
==Make mouse left-click and drag on the image adjust window/level==
 +
 
 +
In older Slicer versions, by default, left-click and drag in a slice view adjusted window/level of the displayed image. Window/level adjustment is now a new mouse mode that can be activated by clicking on its toolbar button or running this code:
  
 
<pre>
 
<pre>
for sliceViewName in layoutManager.sliceViewNames():
+
slicer.app.applicationLogic().GetInteractionNode().SetCurrentInteractionMode(slicer.vtkMRMLInteractionNode.AdjustWindowLevel)
    sliceWidget = layoutManager.sliceWidget(sliceViewName)
+
</pre>
    # setup background volume
 
    compositeNode.SetBackgroundVolumeID(mrVolume.GetID())
 
    # setup foreground volume
 
    compositeNode.SetForegroundVolumeID(ctVolume.GetID())
 
    # change opacity
 
    compositeNode.SetForegroundOpacity(0.3)
 
</pre>
 
 
 
== Synchronize zoom factor between slice views ==
 
  
 +
==Create custom color table==
 +
This example shows how to create a new color table, for example with inverted color range from the default Ocean color table.
 
<pre>
 
<pre>
slicer.sliceNodes = [slicer.app.layoutManager().sliceWidget(viewName).mrmlSliceNode()
+
invertedocean = slicer.vtkMRMLColorTableNode()
    for viewName in slicer.app.layoutManager().sliceViewNames()]
+
invertedocean.SetTypeToUser()
 +
invertedocean.SetNumberOfColors(256)
 +
invertedocean.SetName("InvertedOcean")
  
slicer.updatingSliceNodes = False
+
for i in range(0,255):
 +
    invertedocean.SetColor(i, 0.0, 1 - (i+1e-16)/255.0, 1.0, 1.0)
  
def sliceModified(caller, event):
+
slicer.mrmlScene.AddNode(invertedocean)
    if slicer.updatingSliceNodes:
+
</pre>
        # prevent infinite loop of slice node updates triggering slice node updates
 
        return
 
    slicer.updatingSliceNodes = True
 
    fov = caller.GetFieldOfView()
 
    for sliceNode in slicer.sliceNodes:
 
        if sliceNode != caller:
 
            sliceNode.SetFieldOfView(*fov)
 
    slicer.updatingSliceNodes = False
 
  
for sliceNode in slicer.sliceNodes:
+
==Manipulate a Slice View==
    sliceNode.AddObserver(vtk.vtkCommand.ModifiedEvent, sliceModified)
 
</pre>
 
  
== Show a volume in slice views ==
+
===Change slice offset===
  
Recommended:
+
Equivalent to moving the slider in slice view controller.
  
 
<pre>
 
<pre>
volumeNode = slicer.util.getNode('YourVolumeNode')
+
layoutManager = slicer.app.layoutManager()
slicer.util.setSliceViewerLayers(background=volumeNode)
+
red = layoutManager.sliceWidget('Red')
 +
redLogic = red.sliceLogic()
 +
# Print current slice offset position
 +
print(redLogic.GetSliceOffset())
 +
# Change slice position
 +
redLogic.SetSliceOffset(20)
 
</pre>
 
</pre>
  
or
+
===Change slice orientation===
  
Show volume in all visible views where volume selection propagation is enabled:
+
Get 'Red' slice node and rotate around X and Y axes.
  
 
<pre>
 
<pre>
volumeNode = slicer.util.getNode('YourVolumeNode')
+
sliceNode = slicer.app.layoutManager().sliceWidget('Red').mrmlSliceNode()
applicationLogic = slicer.app.applicationLogic()
+
sliceToRas = sliceNode.GetSliceToRAS()
selectionNode = applicationLogic.GetSelectionNode()
+
transform=vtk.vtkTransform()
selectionNode.SetSecondaryVolumeID(volumeNode.GetID())
+
transform.SetMatrix(SliceToRAS)
applicationLogic.PropagateForegroundVolumeSelection(0)  
+
transform.RotateX(20)
 +
transform.RotateY(15)
 +
sliceToRas.DeepCopy(transform.GetMatrix())
 +
sliceNode.UpdateMatrices()
 
</pre>
 
</pre>
  
or
+
===Show slice views in 3D window===
  
Show volume in selected views:
+
Equivalent to clicking 'eye' icon in the slice view controller.
  
 
<pre>
 
<pre>
n = slicer.util.getNode('YourVolumeNode')
+
layoutManager = slicer.app.layoutManager()
for color in ['Red', 'Yellow', 'Green']:
+
for sliceViewName in layoutManager.sliceViewNames():
    slicer.app.layoutManager().sliceWidget(color).sliceLogic().GetSliceCompositeNode().SetForegroundVolumeID(n.GetID())
+
  controller = layoutManager.sliceWidget(sliceViewName).sliceController()
 +
  controller.setSliceVisible(True)
 
</pre>
 
</pre>
  
== Change opacity of foreground volume in slice views ==
+
===Reset field of view to show background volume maximized===
 +
 
 +
Equivalent to click small rectangle button ("Adjust the slice viewer's field of view...") in the slice view controller.
  
 
<pre>
 
<pre>
slicer.util.setSliceViewerLayers(foregroundOpacity=0.4)
+
slicer.util.resetSliceViews()
 
</pre>
 
</pre>
  
or
+
===Rotate slice views to volume plane===
  
Change opacity in a selected view
+
Aligns slice views to volume axes, shows original image acquisition planes in slice views.
  
 
<pre>
 
<pre>
lm = slicer.app.layoutManager()
+
volumeNode = slicer.util.getNode('MRHead')
sliceLogic = lm.sliceWidget('Red').sliceLogic()
+
layoutManager = slicer.app.layoutManager()
compositeNode = sliceLogic.GetSliceCompositeNode()
+
for sliceViewName in layoutManager.sliceViewNames():
compositeNode.SetForegroundOpacity(0.4)
+
  layoutManager.sliceWidget(sliceViewName).mrmlSliceNode().RotateToVolumePlane(volumeNode)
 
</pre>
 
</pre>
  
== Fit slice plane to markup fiducials ==
+
===Iterate over current visible slice views, and set foreground and background images===
  
 
<pre>
 
<pre>
sliceNode = slicer.mrmlScene.GetNodeByID("vtkMRMLSliceNodeRed")
+
slicer.util.setSliceViewerLayers(background=mrVolume, foreground=ctVolume)
markupsNode = slicer.mrmlScene.GetFirstNodeByName("F")
 
# Get markup point positions as numpy arrays
 
import numpy as np
 
p1 = np.array([0,0,0])
 
p2 = np.array([0,0,0])
 
p3 = np.array([0,0,0])
 
markupsNode.GetNthFiducialPosition(0, p1)
 
markupsNode.GetNthFiducialPosition(1, p2)
 
markupsNode.GetNthFiducialPosition(2, p3)
 
# Get plane axis directions
 
n = np.cross(p2-p1, p2-p3) # plane normal direction
 
n = n/np.linalg.norm(n)
 
t = np.cross([0, 0, 1], n) # plane transverse direction
 
t = t/np.linalg.norm(t)
 
# Set slice plane orientation and position
 
sliceNode.SetSliceToRASByNTP(n[0], n[1], n[2], t[0], t[1], t[2], p1[0], p1[1], p1[2], 0)
 
 
</pre>
 
</pre>
  
== Save a series of images from a Slice View ==
+
Internally, this method performs something like this:
 
 
You can use ScreenCapture module to capture series of images. To do it programmatically, save the following into a file such as '/tmp/record.py' and then in the slicer python console type "execfile('/tmp/record.py')"
 
  
 
<pre>
 
<pre>
layoutName = 'Green'
+
layoutManager = slicer.app.layoutManager()
imagePathPattern = '/tmp/image-%03d.png'
+
for sliceViewName in layoutManager.sliceViewNames():
steps = 10
+
    compositeNode = layoutManager.sliceWidget(sliceViewName).sliceLogic().GetSliceCompositeNode()
 +
    # setup background volume
 +
    compositeNode.SetBackgroundVolumeID(mrVolume.GetID())
 +
    # setup foreground volume
 +
    compositeNode.SetForegroundVolumeID(ctVolume.GetID())
 +
    # change opacity
 +
    compositeNode.SetForegroundOpacity(0.3)
 +
</pre>
 +
 
 +
==Show a volume in slice views==
  
widget = slicer.app.layoutManager().sliceWidget(layoutName)
+
Recommended:
view = widget.sliceView()
 
logic = widget.sliceLogic()
 
bounds = [0,]*6
 
logic.GetSliceBounds(bounds)
 
  
for step in range(steps):
+
<pre>
    offset = bounds[4] + step/(1.*steps) * (bounds[5]-bounds[4])
+
volumeNode = slicer.util.getNode('YourVolumeNode')
    logic.SetSliceOffset(offset)
+
slicer.util.setSliceViewerLayers(background=volumeNode)
    view.forceRender()
 
    image = qt.QPixmap.grabWidget(view).toImage()
 
    image.save(imagePathPattern % step)
 
 
</pre>
 
</pre>
  
== Save the scene into a new directory ==
+
or
 +
 
 +
Show volume in all visible views where volume selection propagation is enabled:
  
 
<pre>
 
<pre>
# Create a new directory where the scene will be saved into
+
volumeNode = slicer.util.getNode('YourVolumeNode')
import time
+
applicationLogic = slicer.app.applicationLogic()
sceneSaveDirectory = slicer.app.temporaryPath + "/saved-scene-" + time.strftime("%Y%m%d-%H%M%S")
+
selectionNode = applicationLogic.GetSelectionNode()
if not os.access(sceneSaveDirectory, os.F_OK):
+
selectionNode.SetSecondaryVolumeID(volumeNode.GetID())
  os.makedirs(sceneSaveDirectory)
+
applicationLogic.PropagateForegroundVolumeSelection(0)  
 +
</pre>
 +
 
 +
or
  
# Save the scene
+
Show volume in selected views:
if slicer.app.applicationLogic().SaveSceneToSlicerDataBundleDirectory(sceneSaveDirectory, None):
 
  logging.info("Scene saved to: {0}".format(sceneSaveDirectory))
 
else:
 
  logging.error("Scene saving failed")
 
</pre>
 
  
== Save the scene into a single MRB file ==
 
 
<pre>
 
<pre>
# Generate file name
+
n = slicer.util.getNode('YourVolumeNode')
import time
+
for color in ['Red', 'Yellow', 'Green']:
sceneSaveFilename = slicer.app.temporaryPath + "/saved-scene-" + time.strftime("%Y%m%d-%H%M%S") + ".mrb"
+
    slicer.app.layoutManager().sliceWidget(color).sliceLogic().GetSliceCompositeNode().SetForegroundVolumeID(n.GetID())
 
 
# Save scene
 
if slicer.util.saveScene(sceneSaveFilename):
 
  logging.info("Scene saved to: {0}".format(sceneSaveFilename))
 
else:
 
  logging.error("Scene saving failed")  
 
 
</pre>
 
</pre>
  
== Save a node to file ==
+
==Change opacity of foreground volume in slice views==
 
 
Save a transform node to file (should work with any other node type, if file extension is set to a supported one):
 
  
 
<pre>
 
<pre>
myNode = getNode("LinearTransform_3")
+
slicer.util.setSliceViewerLayers(foregroundOpacity=0.4)
 
 
myStorageNode = myNode.CreateDefaultStorageNode()
 
myStorageNode.SetFileName("c:/tmp/something.tfm")
 
myStorageNode.WriteData(myNode)
 
 
</pre>
 
</pre>
  
== Center the 3D View on the Scene ==
+
or
<pre>
 
layoutManager = slicer.app.layoutManager()
 
threeDWidget = layoutManager.threeDWidget(0)
 
threeDView = threeDWidget.threeDView()
 
threeDView.resetFocalPoint()
 
</pre>
 
  
==Rotate the 3D View==
+
Change opacity in a selected view
  
 
<pre>
 
<pre>
layoutManager = slicer.app.layoutManager()
+
lm = slicer.app.layoutManager()
threeDWidget = layoutManager.threeDWidget(0)
+
sliceLogic = lm.sliceWidget('Red').sliceLogic()
threeDView = threeDWidget.threeDView()
+
compositeNode = sliceLogic.GetSliceCompositeNode()
threeDView.yaw()
+
compositeNode.SetForegroundOpacity(0.4)
 
</pre>
 
</pre>
  
== Display text in a 3D view or slice view ==
+
==Fit slice plane to markup fiducials==
 
 
The easiest way to show information overlaid on a viewer is to use corner annotations.
 
  
 
<pre>
 
<pre>
view=slicer.app.layoutManager().threeDWidget(0).threeDView()
+
sliceNode = slicer.mrmlScene.GetNodeByID("vtkMRMLSliceNodeRed")
# Set text to "Something"
+
markupsNode = slicer.mrmlScene.GetFirstNodeByName("F")
view.cornerAnnotation().SetText(vtk.vtkCornerAnnotation.UpperRight,"Something")
+
# Get markup point positions as numpy arrays
# Set color to red
+
import numpy as np
view.cornerAnnotation().GetTextProperty().SetColor(1,0,0)
+
p1 = np.zeros(3)
# Update the view
+
p2 = np.zeros(3)
view.forceRender()
+
p3 = np.zeros(3)
 +
markupsNode.GetNthFiducialPosition(0, p1)
 +
markupsNode.GetNthFiducialPosition(1, p2)
 +
markupsNode.GetNthFiducialPosition(2, p3)
 +
# Get plane axis directions
 +
n = np.cross(p2-p1, p2-p3) # plane normal direction
 +
n = n/np.linalg.norm(n)
 +
t = np.cross([0.0, 0.0, 1], n) # plane transverse direction
 +
t = t/np.linalg.norm(t)
 +
# Set slice plane orientation and position
 +
sliceNode.SetSliceToRASByNTP(n[0], n[1], n[2], t[0], t[1], t[2], p1[0], p1[1], p1[2], 0)
 
</pre>
 
</pre>
  
== Hide slice view annotations (DataProbe) ==
+
==Save a series of images from a Slice View==
 +
 
 +
You can use ScreenCapture module to capture series of images. To do it programmatically, save the following into a file such as '/tmp/record.py' and then in the slicer python console type "execfile('/tmp/record.py')"
  
 
<pre>
 
<pre>
# Disable slice annotations immediately
+
layoutName = 'Green'
slicer.modules.DataProbeInstance.infoWidget.sliceAnnotations.sliceViewAnnotationsEnabled=False
+
imagePathPattern = '/tmp/image-%03d.png'
slicer.modules.DataProbeInstance.infoWidget.sliceAnnotations.updateSliceViewFromGUI()
+
steps = 10
# Disable slice annotations persistently (after Slicer restarts)
 
settings = qt.QSettings()
 
settings.setValue('DataProbe/sliceViewAnnotations.enabled', 0)
 
</pre>
 
  
== Turning off interpolation ==
+
widget = slicer.app.layoutManager().sliceWidget(layoutName)
 
+
view = widget.sliceView()
You can turn off interpolation for newly loaded volumes with this script from Steve Pieper.
+
logic = widget.sliceLogic()
 +
bounds = [0,]*6
 +
logic.GetSliceBounds(bounds)
  
<pre>
+
for step in range(steps):
def NoInterpolate(caller,event):
+
    offset = bounds[4] + step/(1.*steps) * (bounds[5]-bounds[4])
  for node in slicer.util.getNodes('*').values():
+
     logic.SetSliceOffset(offset)
     if node.IsA('vtkMRMLScalarVolumeDisplayNode'):
+
    view.forceRender()
      node.SetInterpolate(0)
+
    image = qt.QPixmap.grabWidget(view).toImage()
+
    image.save(imagePathPattern % step)
slicer.mrmlScene.AddObserver(slicer.mrmlScene.NodeAddedEvent, NoInterpolate)
 
 
</pre>
 
</pre>
  
The below link explains how to put this in your startup script.
+
==Rasterize a model and save it to a series of image files==
 
 
http://www.na-mic.org/Wiki/index.php/AHM2012-Slicer-Python#Refining_the_code_and_UI_with_slicerrc
 
  
 +
This example shows how to generate a stack of image files from an STL file:
  
== Customize viewer layout ==
+
inputModelFile = "/some/input/folder/SomeShape.stl"
 +
outputDir = "/some/output/folder"
 +
outputVolumeLabelValue = 100
 +
outputVolumeSpacingMm = [0.5, 0.5, 0.5]
 +
outputVolumeMarginMm = [10.0, 10.0, 10.0]
 +
 +
# Read model
 +
inputModel = slicer.util.loadModel(inputModelFile)
 +
 +
# Determine output volume geometry and create a corresponding reference volume
 +
import math
 +
import numpy as np
 +
bounds = np.zeros(6)
 +
inputModel.GetBounds(bounds)
 +
imageData = vtk.vtkImageData()
 +
imageSize = [ int((bounds[axis*2+1]-bounds[axis*2]+outputVolumeMarginMm[axis]*2.0)/outputVolumeSpacingMm[axis]) for axis in range(3) ]
 +
imageOrigin = [ bounds[axis*2]-outputVolumeMarginMm[axis] for axis in range(3) ]
 +
imageData.SetDimensions(imageSize)
 +
imageData.AllocateScalars(vtk.VTK_UNSIGNED_CHAR, 1)
 +
imageData.GetPointData().GetScalars().Fill(0)
 +
referenceVolumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode")
 +
referenceVolumeNode.SetOrigin(imageOrigin)
 +
referenceVolumeNode.SetSpacing(outputVolumeSpacingMm)
 +
referenceVolumeNode.SetAndObserveImageData(imageData)
 +
referenceVolumeNode.CreateDefaultDisplayNodes()
 +
 +
# Convert model to labelmap
 +
seg = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSegmentationNode')
 +
seg.SetReferenceImageGeometryParameterFromVolumeNode(referenceVolumeNode)
 +
slicer.modules.segmentations.logic().ImportModelToSegmentationNode(inputModel, seg)
 +
seg.CreateBinaryLabelmapRepresentation()
 +
outputLabelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')
 +
slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(seg, outputLabelmapVolumeNode, referenceVolumeNode)
 +
outputLabelmapVolumeArray = (slicer.util.arrayFromVolume(outputLabelmapVolumeNode) * outputVolumeLabelValue).astype('int8')
 +
 +
# Write labelmap volume to series of TIFF files
 +
pip_install("imageio")
 +
import imageio
 +
for i in range(len(outputLabelmapVolumeArray)):
 +
    imageio.imwrite(f'{outputDir}/image_{i:03}.tiff', outputLabelmapVolumeArray[i])
  
Show a custom layout of a 3D view on top of the red slice view:
+
==Save the scene into a new directory==
  
 
<pre>
 
<pre>
customLayout = """
+
# Create a new directory where the scene will be saved into
<layout type="vertical" split="true">
+
import time
  <item>
+
sceneSaveDirectory = slicer.app.temporaryPath + "/saved-scene-" + time.strftime("%Y%m%d-%H%M%S")
  <view class="vtkMRMLViewNode" singletontag="1">
+
if not os.access(sceneSaveDirectory, os.F_OK):
    <property name="viewlabel" action="default">1</property>
+
   os.makedirs(sceneSaveDirectory)
  </view>
 
  </item>
 
  <item>
 
  <view class="vtkMRMLSliceNode" singletontag="Red">
 
    <property name="orientation" action="default">Axial</property>
 
    <property name="viewlabel" action="default">R</property>
 
    <property name="viewcolor" action="default">#F34A33</property>
 
  </view>
 
   </item>
 
</layout>
 
"""
 
  
# Built-in layout IDs are all below 100, so you can choose any large random number
+
# Save the scene
# for your custom layout ID.
+
if slicer.app.applicationLogic().SaveSceneToSlicerDataBundleDirectory(sceneSaveDirectory, None):
customLayoutId=501
+
  logging.info("Scene saved to: {0}".format(sceneSaveDirectory))
 
+
else:
layoutManager = slicer.app.layoutManager()
+
  logging.error("Scene saving failed")  
layoutManager.layoutLogic().GetLayoutNode().AddLayoutDescription(customLayoutId, customLayout)                                        
 
 
 
# Switch to the new custom layout
 
layoutManager.setLayout(customLayoutId)
 
 
</pre>
 
</pre>
  
See description of standard layouts (that can be used as examples) here:
+
==Save the scene into a single MRB file==
https://github.com/Slicer/Slicer/blob/master/Libs/MRML/Logic/vtkMRMLLayoutLogic.cxx
+
<pre>
 +
# Generate file name
 +
import time
 +
sceneSaveFilename = slicer.app.temporaryPath + "/saved-scene-" + time.strftime("%Y%m%d-%H%M%S") + ".mrb"
  
== Customize keyboard shortcuts ==
+
# Save scene
 +
if slicer.util.saveScene(sceneSaveFilename):
 +
  logging.info("Scene saved to: {0}".format(sceneSaveFilename))
 +
else:
 +
  logging.error("Scene saving failed")
 +
</pre>
  
Keyboard shortcuts can be specified for activating any Slicer feature by adding a couple of lines to your
+
==Save a node to file==
[[Documentation/{{documentation/version}}/Developers/Python_scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F|.slicerrc file]].
 
  
For example, this script registers ''Ctrl+b'', ''Ctrl+n'', ''Ctrl+m'', ''Ctrl+,'' keyboard shortcuts to switch between red, yellow, green, and 4-up view layouts.
+
Save a transform node to file (should work with any other node type, if file extension is set to a supported one):
  
 
<pre>
 
<pre>
shortcuts = [
+
myNode = getNode("LinearTransform_3")
    ('Ctrl+b', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpRedSliceView)),
 
    ('Ctrl+n', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpYellowSliceView)),
 
    ('Ctrl+m', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpGreenSliceView)),
 
    ('Ctrl+,', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutFourUpView))
 
    ]
 
  
for (shortcutKey, callback) in shortcuts:
+
myStorageNode = myNode.CreateDefaultStorageNode()
    shortcut = qt.QShortcut(slicer.util.mainWindow())
+
myStorageNode.SetFileName("c:/tmp/something.tfm")
    shortcut.setKey(qt.QKeySequence(shortcutKey))
+
myStorageNode.WriteData(myNode)
    shortcut.connect( 'activated()', callback)
 
 
</pre>
 
</pre>
  
== Disable certain user interactions in slice views ==
+
==Center the 3D View on the Scene==
 
+
<pre>
For example, disable slice browsing using mouse wheel and keyboard shortcuts in the red slice viewer:
+
layoutManager = slicer.app.layoutManager()
 
+
threeDWidget = layoutManager.threeDWidget(0)
<pre>
+
threeDView = threeDWidget.threeDView()
interactorStyle = slicer.app.layoutManager().sliceWidget('Red').sliceView().sliceViewInteractorStyle()
+
threeDView.resetFocalPoint()
interactorStyle.SetActionEnabled(interactorStyle.BrowseSlice, False)
 
 
</pre>
 
</pre>
  
Hide all slice view controllers:
+
==Rotate the 3D View==
<pre>
 
lm = slicer.app.layoutManager()
 
for sliceViewName in lm.sliceViewNames():
 
  lm.sliceWidget(sliceViewName).sliceController().setVisible(False)
 
</pre>
 
  
Hide all 3D view controllers:
 
 
<pre>
 
<pre>
lm = slicer.app.layoutManager()
+
layoutManager = slicer.app.layoutManager()
for viewIndex in range(slicer.app.layoutManager().threeDViewCount):
+
threeDWidget = layoutManager.threeDWidget(0)
  lm.threeDWidget(0).threeDController().setVisible(False)
+
threeDView = threeDWidget.threeDView()
 +
threeDView.yaw()
 
</pre>
 
</pre>
  
== Change default slice view orientation ==
+
==Display text in a 3D view or slice view==
  
You can left-right "flip" slice view orientation presets (show patient left side on left/right side of the screen) by copy-pasting the script below to your [[Documentation/{{documentation/version}}/Developers/FAQ/Python_Scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F| .slicerrc.py file]].
+
The easiest way to show information overlaid on a viewer is to use corner annotations.
  
 
<pre>
 
<pre>
# Axial slice axes:
+
view=slicer.app.layoutManager().threeDWidget(0).threeDView()
#  1 0 0
+
# Set text to "Something"
#  0 1 0
+
view.cornerAnnotation().SetText(vtk.vtkCornerAnnotation.UpperRight,"Something")
#  0 0 1
+
# Set color to red
axialSliceToRas=vtk.vtkMatrix3x3()
+
view.cornerAnnotation().GetTextProperty().SetColor(1,0,0)
 
+
# Update the view
# Coronal slice axes:
+
view.forceRender()
#  1 0 0
 
#  0 0 -1
 
#  0 1 0
 
coronalSliceToRas=vtk.vtkMatrix3x3()
 
coronalSliceToRas.SetElement(1,1, 0)
 
coronalSliceToRas.SetElement(1,2, -1)
 
coronalSliceToRas.SetElement(2,1, 1)
 
coronalSliceToRas.SetElement(2,2, 0)
 
 
 
# Replace orientation presets in all existing slice nodes and in the default slice node
 
sliceNodes = slicer.util.getNodesByClass('vtkMRMLSliceNode')
 
sliceNodes.append(slicer.mrmlScene.GetDefaultNodeByClass('vtkMRMLSliceNode'))
 
for sliceNode in sliceNodes:
 
  orientationPresetName = sliceNode.GetOrientation()
 
  sliceNode.RemoveSliceOrientationPreset("Axial")
 
  sliceNode.AddSliceOrientationPreset("Axial", axialSliceToRas)
 
  sliceNode.RemoveSliceOrientationPreset("Coronal")
 
  sliceNode.AddSliceOrientationPreset("Coronal", coronalSliceToRas)
 
  sliceNode.SetOrientation(orientationPresetName)
 
 
</pre>
 
</pre>
  
 +
==Hide slice view annotations (DataProbe)==
  
== Set all slice views linked by default ==
+
<pre>
 
+
# Disable slice annotations immediately
You can make slice views linked by default (when application starts or the scene is cleared) by copy-pasting the script below to your [[Documentation/{{documentation/version}}/Developers/FAQ/Python_Scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F| .slicerrc.py file]].
+
slicer.modules.DataProbeInstance.infoWidget.sliceAnnotations.sliceViewAnnotationsEnabled=False
 
+
slicer.modules.DataProbeInstance.infoWidget.sliceAnnotations.updateSliceViewFromGUI()
<pre>
+
# Disable slice annotations persistently (after Slicer restarts)
# Set linked slice views  in all existing slice composite nodes and in the default node
+
settings = qt.QSettings()
sliceCompositeNodes = slicer.util.getNodesByClass('vtkMRMLSliceCompositeNode')
+
settings.setValue('DataProbe/sliceViewAnnotations.enabled', 0)
defaultSliceCompositeNode = slicer.mrmlScene.GetDefaultNodeByClass('vtkMRMLSliceCompositeNode')
 
if not defaultSliceCompositeNode:
 
  defaultSliceCompositeNode = slicer.mrmlScene.CreateNodeByClass('vtkMRMLSliceCompositeNode')
 
  slicer.mrmlScene.AddDefaultNode(defaultSliceCompositeNode)
 
sliceCompositeNodes.append(defaultSliceCompositeNode)
 
for sliceCompositeNode in sliceCompositeNodes:
 
  sliceCompositeNode.SetLinkedControl(True)
 
 
</pre>
 
</pre>
  
== Set crosshair jump mode to centered by default ==
+
==Turning off interpolation==
  
You can change default slice jump mode (when application starts or the scene is cleared) by copy-pasting the script below to your [[Documentation/{{documentation/version}}/Developers/FAQ/Python_Scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F| .slicerrc.py file]].
+
You can turn off interpolation for newly loaded volumes with this script from Steve Pieper.
  
 
<pre>
 
<pre>
crosshair=slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLCrosshairNode")
+
def NoInterpolate(caller,event):
crosshair.SetCrosshairBehavior(crosshair.CenteredJumpSlice)
+
  for node in slicer.util.getNodes('*').values():
 +
    if node.IsA('vtkMRMLScalarVolumeDisplayNode'):
 +
      node.SetInterpolate(0)
 +
 +
slicer.mrmlScene.AddObserver(slicer.mrmlScene.NodeAddedEvent, NoInterpolate)
 
</pre>
 
</pre>
  
== Set up custom units in slice view ruler ==
+
The below link explains how to put this in your startup script.
 +
 
 +
http://www.na-mic.org/Wiki/index.php/AHM2012-Slicer-Python#Refining_the_code_and_UI_with_slicerrc
  
For microscopy or micro-CT images you may want to switch unit to micrometer instead of the default mm. To do that, 1. change the unit in Application settings / Units and 2. update ruler display settings using the script below (it can be copied to your Application startup script):
 
  
<pre>
+
==Customize viewer layout==
lm = slicer.app.layoutManager()
 
for sliceViewName in lm.sliceViewNames():
 
  sliceView = lm.sliceWidget(sliceViewName).sliceView()
 
  displayableManagerCollection = vtk.vtkCollection()
 
  sliceView.getDisplayableManagers(displayableManagerCollection)
 
  for dmIndex in range(displayableManagerCollection.GetNumberOfItems()):
 
    displayableManager = displayableManagerCollection.GetItemAsObject(dmIndex)
 
    if not displayableManager.IsA("vtkMRMLRulerDisplayableManager"):
 
      continue
 
    displayableManager.RemoveAllRulerScalePresets()
 
    displayableManager.AddRulerScalePreset(  0.001, 5, 2, "nm", 1000.0)
 
    displayableManager.AddRulerScalePreset(  0.010, 5, 2, "nm", 1000.0)
 
    displayableManager.AddRulerScalePreset(  0.100, 5, 2, "nm", 1000.0)
 
    displayableManager.AddRulerScalePreset(  0.500, 5, 1, "nm", 1000.0)
 
    displayableManager.AddRulerScalePreset(  1.0,  5, 2, "um",    1.0)
 
    displayableManager.AddRulerScalePreset(  5.0,  5, 1, "um",    1.0)
 
    displayableManager.AddRulerScalePreset(  10.0,  5, 2, "um",    1.0)
 
    displayableManager.AddRulerScalePreset(  50.0,  5, 1, "um",    1.0)
 
    displayableManager.AddRulerScalePreset( 100.0,  5, 2, "um",    1.0)
 
    displayableManager.AddRulerScalePreset( 500.0,  5, 1, "um",    1.0)
 
    displayableManager.AddRulerScalePreset(1000.0,  5, 2, "mm",    0.001)
 
</pre>
 
  
== Show a slice view outside the view layout ==
+
Show a custom layout of a 3D view on top of the red slice view:
  
 
<pre>
 
<pre>
layoutName = "TestSlice"
+
customLayout = """
layoutLabel = "TS"
+
<layout type="vertical" split="true">
# ownerNode manages this view instead of the layout manager (it can be any node in the scene)
+
  <item>
viewOwnerNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScriptedModuleNode")
+
  <view class="vtkMRMLViewNode" singletontag="1">
 +
    <property name="viewlabel" action="default">1</property>
 +
  </view>
 +
  </item>
 +
  <item>
 +
  <view class="vtkMRMLSliceNode" singletontag="Red">
 +
    <property name="orientation" action="default">Axial</property>
 +
    <property name="viewlabel" action="default">R</property>
 +
    <property name="viewcolor" action="default">#F34A33</property>
 +
  </view>
 +
  </item>
 +
</layout>
 +
"""
  
# Create MRML nodes
+
# Built-in layout IDs are all below 100, so you can choose any large random number
viewNode = slicer.vtkMRMLSliceNode()
+
# for your custom layout ID.
viewNode.SetName(layoutName)
+
customLayoutId=501
viewNode.SetLayoutName(layoutName)
 
viewNode.SetLayoutLabel(layoutLabel)
 
viewNode.SetLayoutColor(1, 1, 0)
 
viewNode.SetAndObserveParentLayoutNodeID(viewOwnerNode.GetID())
 
viewNode = slicer.mrmlScene.AddNode(viewNode)
 
sliceCompositeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSliceCompositeNode")
 
sliceCompositeNode.SetLayoutName(layoutName)
 
  
# Create widget
+
layoutManager = slicer.app.layoutManager()
viewWidget = slicer.qMRMLSliceWidget()
+
layoutManager.layoutLogic().GetLayoutNode().AddLayoutDescription(customLayoutId, customLayout)                                        
viewWidget.sliceViewName = layoutName
+
 
viewWidget.sliceViewLabel = layoutLabel
+
# Switch to the new custom layout
c = viewNode.GetLayoutColor()
+
layoutManager.setLayout(customLayoutId)
viewWidget.sliceViewColor = qt.QColor.fromRgbF(c[0],c[1],c[2])
 
viewWidget.setMRMLScene(slicer.mrmlScene)
 
viewWidget.setMRMLSliceNode(viewNode)
 
viewWidget.show()
 
 
</pre>
 
</pre>
  
== Show a 3D view outside the view layout ==
+
See description of standard layouts (that can be used as examples) here:
 +
https://github.com/Slicer/Slicer/blob/master/Libs/MRML/Logic/vtkMRMLLayoutLogic.cxx
 +
 
 +
You can use this code snippet to add a button to the layout selector toolbar:
  
 
<pre>
 
<pre>
layoutName = "Test3DView"
+
# Add button to layout selector toolbar for this custom layout
layoutLabel = "T3"
+
viewToolBar = mainWindow().findChild('QToolBar', 'ViewToolBar')
# ownerNode manages this view instead of the layout manager (it can be any node in the scene)
+
layoutMenu = viewToolBar.widgetForAction(viewToolBar.actions()[0]).menu()
viewOwnerNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScriptedModuleNode")
+
layoutSwitchActionParent = layoutMenu  # use `layoutMenu` to add inside layout list, use `viewToolBar` to add next the standard layout list
 +
layoutSwitchAction = layoutSwitchActionParent.addAction("My view") # add inside layout list
 +
layoutSwitchAction.setData(layoutId)
 +
layoutSwitchAction.setIcon(qt.QIcon(':Icons/Go.png'))
 +
layoutSwitchAction.setToolTip('3D and slice view')
 +
layoutSwitchAction.connect('triggered()', lambda layoutId = customLayoutId: slicer.app.layoutManager().setLayout(layoutId))
 +
</pre>
  
# Create MRML node
+
==Customize keyboard shortcuts==
viewNode = slicer.vtkMRMLViewNode()
 
viewNode.SetName(layoutName)
 
viewNode.SetLayoutName(layoutName)
 
viewNode.SetLayoutLabel(layoutLabel)
 
viewNode.SetLayoutColor(1, 1, 0)
 
viewNode.SetAndObserveParentLayoutNodeID(viewOwnerNode.GetID())
 
viewNode = slicer.mrmlScene.AddNode(viewNode)
 
  
# Create widget
+
Keyboard shortcuts can be specified for activating any Slicer feature by adding a couple of lines to your
viewWidget = slicer.qMRMLThreeDWidget()
+
[[Documentation/{{documentation/version}}/Developers/Python_scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F|.slicerrc file]].
viewWidget.viewLabel = layoutLabel
+
 
viewWidget.viewColor = qt.QColor.fromRgbF(c[0],c[1],c[2])
+
For example, this script registers ''Ctrl+b'', ''Ctrl+n'', ''Ctrl+m'', ''Ctrl+,'' keyboard shortcuts to switch between red, yellow, green, and 4-up view layouts.
viewWidget.setMRMLScene(slicer.mrmlScene)
+
 
viewWidget.setMRMLViewNode(viewNode)
+
<pre>
viewWidget.show()
+
shortcuts = [
 +
    ('Ctrl+b', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpRedSliceView)),
 +
    ('Ctrl+n', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpYellowSliceView)),
 +
    ('Ctrl+m', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpGreenSliceView)),
 +
    ('Ctrl+,', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutFourUpView))
 +
    ]
 +
 
 +
for (shortcutKey, callback) in shortcuts:
 +
    shortcut = qt.QShortcut(slicer.util.mainWindow())
 +
    shortcut.setKey(qt.QKeySequence(shortcutKey))
 +
    shortcut.connect( 'activated()', callback)
 
</pre>
 
</pre>
  
== Running an ITK filter in Python using SimpleITK ==
+
Here's an example for cycling through Segment Editor effects (requested [https://discourse.slicer.org/t/is-there-a-keystroke-to-cycle-through-effects-in-segment-editor/10117/2 on the forum] for the [http://slicermorph.org SlicerMorph] project).
Open the "Sample Data" module and download "MR Head", then paste the following snippet in Python interactor:
 
 
<pre>
 
<pre>
import SampleData
+
def cycleEffect(delta=1):
import SimpleITK as sitk
+
    try:
import sitkUtils
+
        orderedNames = list(slicer.modules.SegmentEditorWidget.editor.effectNameOrder())
 +
        allNames = slicer.modules.SegmentEditorWidget.editor.availableEffectNames()
 +
        for name in allNames:
 +
            try:
 +
                orderedNames.index(name)
 +
            except ValueError:
 +
                orderedNames.append(name)
 +
        orderedNames.insert(0, None)
 +
        activeEffect = slicer.modules.SegmentEditorWidget.editor.activeEffect()
 +
        if activeEffect:
 +
            activeName = slicer.modules.SegmentEditorWidget.editor.activeEffect().name
 +
        else:
 +
            activeName = None
 +
        newIndex = (orderedNames.index(activeName) + delta) % len(orderedNames)
 +
        slicer.modules.SegmentEditorWidget.editor.setActiveEffectByName(orderedNames[newIndex])
 +
    except AttributeError:
 +
        # module not active
 +
        pass
  
# Get input volume node
+
shortcuts = [
inputVolumeNode = SampleData.SampleDataLogic().downloadMRHead()
+
    ('`', lambda: cycleEffect(-1)),
# Create new volume node for output
+
    ('~', lambda: cycleEffect(1)),
outputVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLScalarVolumeNode', 'MRHeadFiltered')
+
    ]
  
# Run processing
+
for (shortcutKey, callback) in shortcuts:
inputImage = sitkUtils.PullVolumeFromSlicer(inputVolumeNode)
+
    shortcut = qt.QShortcut(slicer.util.mainWindow())
filter = sitk.SignedMaurerDistanceMapImageFilter()
+
    shortcut.setKey(qt.QKeySequence(shortcutKey))
outputImage = filter.Execute(inputImage)
+
    shortcut.connect( 'activated()', callback)
sitkUtils.PushVolumeToSlicer(outputImage, outputVolumeNode)
 
 
 
# Show processing result
 
slicer.util.setSliceViewerLayers(background=outputVolumeNode)
 
 
</pre>
 
</pre>
  
More information:
+
==Disable certain user interactions in slice views==
* See the SimpleITK documentation for SimpleITK examples: http://www.itk.org/SimpleITKDoxygen/html/examples.html
 
* sitkUtils in Slicer is used for pushing and pulling images from Slicer to SimpleITK: https://github.com/Slicer/Slicer/blob/master/Base/Python/sitkUtils.py
 
  
== Get current mouse coordinates in a slice view ==
+
For example, disable slice browsing using mouse wheel and keyboard shortcuts in the red slice viewer:
 
 
You can get 3D (RAS) coordinates of the current mouse cursor from the crosshair singleton node as shown in the example below:
 
  
 
<pre>
 
<pre>
def onMouseMoved(observer,eventid)
+
interactorStyle = slicer.app.layoutManager().sliceWidget('Red').sliceView().sliceViewInteractorStyle()
  ras=[0,0,0]
+
interactorStyle.SetActionEnabled(interactorStyle.BrowseSlice, False)
  crosshairNode.GetCursorPositionRAS(ras)
 
  print(ras)
 
 
 
crosshairNode=slicer.util.getNode('Crosshair')  
 
crosshairNode.AddObserver(slicer.vtkMRMLCrosshairNode.CursorPositionModifiedEvent, onMouseMoved)
 
 
</pre>
 
</pre>
  
== Get DataProbe text ==
+
Hide all slice view controllers:
 
+
<pre>
You can get the mouse location in pixel coordinates along with the pixel value at the mouse by hitting the '.' (period) key in a slice view after pasting in the following code.
+
lm = slicer.app.layoutManager()
 +
for sliceViewName in lm.sliceViewNames():
 +
  lm.sliceWidget(sliceViewName).sliceController().setVisible(False)
 +
</pre>
  
 +
Hide all 3D view controllers:
 
<pre>
 
<pre>
def printDataProbe():
+
lm = slicer.app.layoutManager()
  infoWidget = slicer.modules.DataProbeInstance.infoWidget
+
for viewIndex in range(slicer.app.layoutManager().threeDViewCount):
  for layer in ('B', 'F', 'L'):
+
  lm.threeDWidget(0).threeDController().setVisible(False)
    print(infoWidget.layerNames[layer].text, infoWidget.layerIJKs[layer].text, infoWidget.layerValues[layer].text)
+
</pre>
  
s = qt.QShortcut(qt.QKeySequence('.'), mainWindow())
+
==Change default slice view orientation==
s.connect('activated()', printDataProbe)
 
</pre>
 
  
== Get reformatted image from a slice viewer as numpy array ==
+
You can left-right "flip" slice view orientation presets (show patient left side on left/right side of the screen) by copy-pasting the script below to your [[Documentation/{{documentation/version}}/Developers/FAQ/Python_Scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F| .slicerrc.py file]].
  
Set up 'red' slice viewer to show thick slab reconstructed from 3 slices:
 
 
<pre>
 
<pre>
sliceNodeID = 'vtkMRMLSliceNodeRed'
+
# Axial slice axes:
 +
#  1 0 0
 +
#  0 1 0
 +
#  0 0 1
 +
axialSliceToRas=vtk.vtkMatrix3x3()
  
# Get image data from slice view
+
# Coronal slice axes:
sliceNode = slicer.mrmlScene.GetNodeByID(sliceNodeID)
+
#  1 0 0
appLogic = slicer.app.applicationLogic()
+
#  0 0 -1
sliceLogic = appLogic.GetSliceLogic(sliceNode)
+
#  0 1 0
sliceLayerLogic = sliceLogic.GetBackgroundLayer()
+
coronalSliceToRas=vtk.vtkMatrix3x3()
reslice = sliceLayerLogic.GetReslice()
+
coronalSliceToRas.SetElement(1,1, 0)
reslicedImage = vtk.vtkImageData()
+
coronalSliceToRas.SetElement(1,2, -1)
reslicedImage.DeepCopy(reslice.GetOutput())
+
coronalSliceToRas.SetElement(2,1, 1)
 +
coronalSliceToRas.SetElement(2,2, 0)
  
# Create new volume node using resliced image
+
# Replace orientation presets in all existing slice nodes and in the default slice node
volumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode")
+
sliceNodes = slicer.util.getNodesByClass('vtkMRMLSliceNode')
volumeNode.SetIJKToRASMatrix(sliceNode.GetXYToRAS())
+
sliceNodes.append(slicer.mrmlScene.GetDefaultNodeByClass('vtkMRMLSliceNode'))
volumeNode.SetAndObserveImageData(reslicedImage)
+
for sliceNode in sliceNodes:
volumeNode.CreateDefaultDisplayNodes()
+
  orientationPresetName = sliceNode.GetOrientation()
volumeNode.CreateDefaultStorageNode()
+
  sliceNode.RemoveSliceOrientationPreset("Axial")
 
+
  sliceNode.AddSliceOrientationPreset("Axial", axialSliceToRas)
# Get voxels as a numpy array
+
  sliceNode.RemoveSliceOrientationPreset("Coronal")
voxels = slicer.util.arrayFromVolume(volumeNode)
+
  sliceNode.AddSliceOrientationPreset("Coronal", coronalSliceToRas)
print voxels.shape
+
  sliceNode.SetOrientation(orientationPresetName)
 
</pre>
 
</pre>
  
== Thick slab reconstruction and maximum/minimum intensity volume projections ==
 
  
Set up 'red' slice viewer to show thick slab reconstructed from 3 slices:
+
==Set all slice views linked by default==
 +
 
 +
You can make slice views linked by default (when application starts or the scene is cleared) by copy-pasting the script below to your [[Documentation/{{documentation/version}}/Developers/FAQ/Python_Scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F| .slicerrc.py file]].
 +
 
 
<pre>
 
<pre>
sliceNode = slicer.mrmlScene.GetNodeByID('vtkMRMLSliceNodeRed')
+
# Set linked slice views  in all existing slice composite nodes and in the default node
appLogic = slicer.app.applicationLogic()
+
sliceCompositeNodes = slicer.util.getNodesByClass('vtkMRMLSliceCompositeNode')
sliceLogic = appLogic.GetSliceLogic(sliceNode)
+
defaultSliceCompositeNode = slicer.mrmlScene.GetDefaultNodeByClass('vtkMRMLSliceCompositeNode')
sliceLayerLogic = sliceLogic.GetBackgroundLayer()
+
if not defaultSliceCompositeNode:
reslice = sliceLayerLogic.GetReslice()
+
  defaultSliceCompositeNode = slicer.mrmlScene.CreateNodeByClass('vtkMRMLSliceCompositeNode')
reslice.SetSlabModeToMean()
+
  slicer.mrmlScene.AddDefaultNode(defaultSliceCompositeNode)
reslice.SetSlabNumberOfSlices(10) # mean of 10 slices will computed
+
sliceCompositeNodes.append(defaultSliceCompositeNode)
reslice.SetSlabSliceSpacingFraction(0.3) # spacing between each slice is 0.3 pixel (total 10 * 0.3 = 3 pixel neighborhood)
+
for sliceCompositeNode in sliceCompositeNodes:
sliceNode.Modified()
+
  sliceCompositeNode.SetLinkedControl(True)
 
</pre>
 
</pre>
  
Set up 'red' slice viewer to show maximum intensity projection (MIP):
+
==Set crosshair jump mode to centered by default==
<pre>
+
 
sliceNode = slicer.mrmlScene.GetNodeByID('vtkMRMLSliceNodeRed')
+
You can change default slice jump mode (when application starts or the scene is cleared) by copy-pasting the script below to your [[Documentation/{{documentation/version}}/Developers/FAQ/Python_Scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F| .slicerrc.py file]].
appLogic = slicer.app.applicationLogic()
 
sliceLogic = appLogic.GetSliceLogic(sliceNode)
 
sliceLayerLogic = sliceLogic.GetBackgroundLayer()
 
reslice = sliceLayerLogic.GetReslice()
 
reslice.SetSlabModeToMax()
 
reslice.SetSlabNumberOfSlices(600) # use a large number of slices (600) to cover the entire volume
 
reslice.SetSlabSliceSpacingFraction(0.5) # spacing between slices are 0.5 pixel (supersampling is useful to reduce interpolation artifacts)
 
sliceNode.Modified()
 
</pre>
 
  
The projected image is available in a ''vtkImageData'' object by calling ''reslice.GetOutput()''.
 
 
== Change default file type for nodes (that have never been saved yet) ==
 
Default node can be specified that will be used as a basis of all new storage nodes. This can be used for setting default file extension. For example, change file format to STL for model nodes:
 
 
<pre>
 
<pre>
defaultModelStorageNode = slicer.vtkMRMLModelStorageNode()
+
crosshair=slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLCrosshairNode")
defaultModelStorageNode.SetDefaultWriteFileExtension('stl')
+
crosshair.SetCrosshairBehavior(crosshair.CenteredJumpSlice)
slicer.mrmlScene.AddDefaultNode(defaultModelStorageNode)
 
 
</pre>
 
</pre>
  
To permanently change default file extension on your computer, copy-paste the code above into your application startup script (you can find its location in menu: Edit / Application settings / General / Application startup script).
+
==Set up custom units in slice view ruler==
  
== Change file type for saving for all volumes (with already existing storage nodes) ==
+
For microscopy or micro-CT images you may want to switch unit to micrometer instead of the default mm. To do that, 1. change the unit in Application settings / Units and 2. update ruler display settings using the script below (it can be copied to your Application startup script):
 
 
If it is not necessary to preserve file paths then the simplest is to configure default storage node (as shown in the example above), then delete all existing storage nodes. When save dialog is opened, default storage nodes will be recreated.
 
  
 
<pre>
 
<pre>
# Delete existing model storage nodes so that they will be recreated with default settings
+
lm = slicer.app.layoutManager()
existingModelStorageNodes = slicer.util.getNodesByClass('vtkMRMLModelStorageNode')
+
for sliceViewName in lm.sliceViewNames():
for modelStorageNode in existingModelStorageNodes:
+
  sliceView = lm.sliceWidget(sliceViewName).sliceView()
   slicer.mrmlScene.RemoveNode(modelStorageNode)
+
  displayableManagerCollection = vtk.vtkCollection()
 +
  sliceView.getDisplayableManagers(displayableManagerCollection)
 +
  for dmIndex in range(displayableManagerCollection.GetNumberOfItems()):
 +
    displayableManager = displayableManagerCollection.GetItemAsObject(dmIndex)
 +
    if not displayableManager.IsA("vtkMRMLRulerDisplayableManager"):
 +
      continue
 +
    displayableManager.RemoveAllRulerScalePresets()
 +
    displayableManager.AddRulerScalePreset(   0.001, 5, 2, "nm", 1000.0)
 +
    displayableManager.AddRulerScalePreset(  0.010, 5, 2, "nm", 1000.0)
 +
    displayableManager.AddRulerScalePreset(  0.100, 5, 2, "nm", 1000.0)
 +
    displayableManager.AddRulerScalePreset(  0.500, 5, 1, "nm", 1000.0)
 +
    displayableManager.AddRulerScalePreset(  1.0,  5, 2, "um",    1.0)
 +
    displayableManager.AddRulerScalePreset(  5.0,  5, 1, "um",    1.0)
 +
    displayableManager.AddRulerScalePreset(  10.0,  5, 2, "um",    1.0)
 +
    displayableManager.AddRulerScalePreset(  50.0,  5, 1, "um",    1.0)
 +
    displayableManager.AddRulerScalePreset( 100.0,  5, 2, "um",    1.0)
 +
    displayableManager.AddRulerScalePreset( 500.0,  5, 1, "um",    1.0)
 +
    displayableManager.AddRulerScalePreset(1000.0,  5, 2, "mm",    0.001)
 
</pre>
 
</pre>
  
To update existing storage nodes to use new file extension (but keep all other parameters unchanged) you can use this approach (example is for volume storage):
+
==Show a slice view outside the view layout==
  
 
<pre>
 
<pre>
requiredFileExtension = '.nia'
+
layoutName = "TestSlice"
originalFileExtension = '.nrrd'
+
layoutLabel = "TS"
volumeNodes = slicer.util.getNodesByClass('vtkMRMLScalarVolumeNode')
+
# ownerNode manages this view instead of the layout manager (it can be any node in the scene)
for volumeNode in volumeNodes:
+
viewOwnerNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScriptedModuleNode")
  volumeStorageNode = volumeNode.GetStorageNode()
+
 
  if not volumeStorageNode:
+
# Create MRML nodes
    volumeNode.AddDefaultStorageNode()
+
viewNode = slicer.vtkMRMLSliceNode()
    volumeStorageNode = volumeNode.GetStorageNode()
+
viewNode.SetName(layoutName)
    volumeStorageNode.SetFileName(volumeNode.GetName()+requiredFileExtension)
+
viewNode.SetLayoutName(layoutName)
  else:
+
viewNode.SetLayoutLabel(layoutLabel)
    volumeStorageNode.SetFileName(volumeStorageNode.GetFileName().replace(originalFileExtension, requiredFileExtension))
+
viewNode.SetLayoutColor(1, 1, 0)
 +
viewNode.SetAndObserveParentLayoutNodeID(viewOwnerNode.GetID())
 +
viewNode = slicer.mrmlScene.AddNode(viewNode)
 +
sliceCompositeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSliceCompositeNode")
 +
sliceCompositeNode.SetLayoutName(layoutName)
 +
 
 +
# Create widget
 +
viewWidget = slicer.qMRMLSliceWidget()
 +
viewWidget.sliceViewName = layoutName
 +
viewWidget.sliceViewLabel = layoutLabel
 +
c = viewNode.GetLayoutColor()
 +
viewWidget.sliceViewColor = qt.QColor.fromRgbF(c[0],c[1],c[2])
 +
viewWidget.setMRMLScene(slicer.mrmlScene)
 +
viewWidget.setMRMLSliceNode(viewNode)
 +
viewWidget.show()
 
</pre>
 
</pre>
  
To set all volume nodes to save uncompressed by default (add this to .slicerrc.py so it takes effect for the whole session):
+
==Show a 3D view outside the view layout==
 +
 
 
<pre>
 
<pre>
#set the default volume storage to not compress by default
+
layoutName = "Test3DView"
defaultVolumeStorageNode = slicer.vtkMRMLVolumeArchetypeStorageNode()
+
layoutLabel = "T3"
defaultVolumeStorageNode.SetUseCompression(0)
+
# ownerNode manages this view instead of the layout manager (it can be any node in the scene)
slicer.mrmlScene.AddDefaultNode(defaultVolumeStorageNode)
+
viewOwnerNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScriptedModuleNode")
logging.info("Volume nodes will be stored uncompressed by default")
+
 
 +
# Create MRML node
 +
viewNode = slicer.vtkMRMLViewNode()
 +
viewNode.SetName(layoutName)
 +
viewNode.SetLayoutName(layoutName)
 +
viewNode.SetLayoutLabel(layoutLabel)
 +
viewNode.SetLayoutColor(1, 1, 0)
 +
viewNode.SetAndObserveParentLayoutNodeID(viewOwnerNode.GetID())
 +
viewNode = slicer.mrmlScene.AddNode(viewNode)
 +
 
 +
# Create widget
 +
viewWidget = slicer.qMRMLThreeDWidget()
 +
viewWidget.viewLabel = layoutLabel
 +
viewWidget.viewColor = qt.QColor.fromRgbF(c[0],c[1],c[2])
 +
viewWidget.setMRMLScene(slicer.mrmlScene)
 +
viewWidget.setMRMLViewNode(viewNode)
 +
viewWidget.show()
 
</pre>
 
</pre>
  
Same thing as above, but applied to all  segmentations instead of volumes:
+
==Get displayable manager of a certain type for a certain view==
 +
 
 
<pre>
 
<pre>
#set the default volume storage to not compress by default
+
modelDisplayableManager = None
defaultVolumeStorageNode = slicer.vtkMRMLSegmentationStorageNode()
+
threeDViewWidget = slicer.app.layoutManager().threeDWidget(0)
defaultVolumeStorageNode.SetUseCompression(0)
+
managers = vtk.vtkCollection()
slicer.mrmlScene.AddDefaultNode(defaultVolumeStorageNode)
+
threeDViewWidget.getDisplayableManagers(managers)
logging.info("Segmentation nodes will be stored uncompressed
+
for i in range(managers.GetNumberOfItems()):
 +
  obj = managers.GetItemAsObject(i)
 +
  if obj.IsA('vtkMRMLModelDisplayableManager'):
 +
    modelDisplayableManager = obj
 +
    break
 +
if modelDisplayableManager is None:
 +
  logging.error('Failed to find the model displayable manager')
 +
  return
 
</pre>
 
</pre>
  
== Sequences ==
+
==Running an ITK filter in Python using SimpleITK==
 +
Open the "Sample Data" module and download "MR Head", then paste the following snippet in Python interactor:
 +
<pre>
 +
import SampleData
 +
import SimpleITK as sitk
 +
import sitkUtils
  
=== Concatenate all sequences in the scene into a new sequence ===
+
# Get input volume node
 +
inputVolumeNode = SampleData.SampleDataLogic().downloadMRHead()
 +
# Create new volume node for output
 +
outputVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLScalarVolumeNode', 'MRHeadFiltered')
 +
 
 +
# Run processing
 +
inputImage = sitkUtils.PullVolumeFromSlicer(inputVolumeNode)
 +
filter = sitk.SignedMaurerDistanceMapImageFilter()
 +
outputImage = filter.Execute(inputImage)
 +
sitkUtils.PushVolumeToSlicer(outputImage, outputVolumeNode)
  
<pre>
+
# Show processing result
# Get all sequence nodes in the scene
+
slicer.util.setSliceViewerLayers(background=outputVolumeNode)
sequenceNodes = slicer.util.getNodesByClass('vtkMRMLSequenceNode')
+
</pre>
mergedSequenceNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSequenceNode', 'Merged sequence')
 
  
# Merge all sequence nodes into a new sequence node
+
More information:
mergedIndexValue = 0
 
for sequenceNode in sequenceNodes:
 
    for itemIndex in range(sequenceNode.GetNumberOfDataNodes()):
 
        dataNode = sequenceNode.GetNthDataNode(itemIndex)
 
        mergedSequenceNode.SetDataNodeAtValue(dataNode, str(mergedIndexValue))
 
        mergedIndexValue += 1
 
    # Delete the sequence node we copied the data from, to prevent sharing of the same
 
    # node by multiple sequences
 
    slicer.mrmlScene.RemoveNode(sequenceNode)
 
  
# Create a sequence browser node for the new merged sequence
+
*See the SimpleITK documentation for SimpleITK examples: http://www.itk.org/SimpleITKDoxygen/html/examples.html
mergedSequenceBrowserNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSequenceBrowserNode', 'Merged')
+
*sitkUtils in Slicer is used for pushing and pulling images from Slicer to SimpleITK: https://github.com/Slicer/Slicer/blob/master/Base/Python/sitkUtils.py
mergedSequenceBrowserNode.AddSynchronizedSequenceNode(mergedSequenceNode)
 
slicer.modules.sequencebrowser.setToolBarActiveBrowserNode(mergedSequenceBrowserNode)
 
# Show proxy node in slice viewers
 
mergedProxyNode = mergedSequenceBrowserNode.GetProxyNode(mergedSequenceNode)
 
slicer.util.setSliceViewerLayers(background=mergedProxyNode)
 
</pre>
 
  
== Segmentations ==
+
==Get current mouse coordinates in a slice view==
  
=== Create a segmentation from a labelmap volume and display in 3D ===
+
You can get 3D (RAS) coordinates of the current mouse cursor from the crosshair singleton node as shown in the example below:
  
 
<pre>
 
<pre>
labelmapVolumeNode = getNode('label')
+
def onMouseMoved(observer,eventid)
seg = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSegmentationNode')
+
  ras=[0,0,0]
slicer.modules.segmentations.logic().ImportLabelmapToSegmentationNode(labelmapVolumeNode, seg)
+
  crosshairNode.GetCursorPositionRAS(ras)
seg.CreateClosedSurfaceRepresentation()
+
  print(ras)
slicer.mrmlScene.RemoveNode(labelmapVolumeNode)
+
 
 +
crosshairNode=slicer.util.getNode('Crosshair')  
 +
crosshairNode.AddObserver(slicer.vtkMRMLCrosshairNode.CursorPositionModifiedEvent, onMouseMoved)
 
</pre>
 
</pre>
  
The last line is optional. It removes the original labelmap volume so that the same information is not shown twice.
+
==Get DataProbe text==
  
=== Export labelmap node from segmentation node ===
+
You can get the mouse location in pixel coordinates along with the pixel value at the mouse by hitting the '.' (period) key in a slice view after pasting in the following code.
 
 
Export smallest possible labelmap:
 
  
 
<pre>
 
<pre>
seg = getNode('Segmentation')
+
def printDataProbe():
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')
+
  infoWidget = slicer.modules.DataProbeInstance.infoWidget
slicer.modules.segmentations.logic().ExportAllSegmentsToLabelmapNode(seg, labelmapVolumeNode)
+
  for layer in ('B', 'F', 'L'):
</pre>
+
    print(infoWidget.layerNames[layer].text, infoWidget.layerIJKs[layer].text, infoWidget.layerValues[layer].text)
  
Export labelmap that matches geometry of a chosen reference volume:
+
s = qt.QShortcut(qt.QKeySequence('.'), mainWindow())
 
+
s.connect('activated()', printDataProbe)
<pre>
 
seg = getNode('Segmentation')
 
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')
 
slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, referenceVolumeNode)
 
 
</pre>
 
</pre>
  
Export by pressing Ctrl+Shift+s key:
+
==Get reformatted image from a slice viewer as numpy array==
  
 +
Set up 'red' slice viewer to show thick slab reconstructed from 3 slices:
 
<pre>
 
<pre>
outputPath = "c:/tmp"
+
sliceNodeID = 'vtkMRMLSliceNodeRed'
  
def exportLabelmap():
+
# Get image data from slice view
    segmentationNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLSegmentationNode")
+
sliceNode = slicer.mrmlScene.GetNodeByID(sliceNodeID)
    referenceVolumeNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLScalarVolumeNode")
+
appLogic = slicer.app.applicationLogic()
    labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')
+
sliceLogic = appLogic.GetSliceLogic(sliceNode)
    slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, referenceVolumeNode)
+
sliceLayerLogic = sliceLogic.GetBackgroundLayer()
    filepath = outputPath + "/" + referenceVolumeNode.GetName()+"-label.nrrd"
+
reslice = sliceLayerLogic.GetReslice()
    slicer.util.saveNode(labelmapVolumeNode, filepath)
+
reslicedImage = vtk.vtkImageData()
    slicer.mrmlScene.RemoveNode(labelmapVolumeNode.GetDisplayNode().GetColorNode())
+
reslicedImage.DeepCopy(reslice.GetOutput())
    slicer.mrmlScene.RemoveNode(labelmapVolumeNode)
+
 
    slicer.util.delayDisplay("Segmentation saved to "+filepath)
+
# Create new volume node using resliced image
 +
volumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode")
 +
volumeNode.SetIJKToRASMatrix(sliceNode.GetXYToRAS())
 +
volumeNode.SetAndObserveImageData(reslicedImage)
 +
volumeNode.CreateDefaultDisplayNodes()
 +
volumeNode.CreateDefaultStorageNode()
  
shortcut = qt.QShortcut(slicer.util.mainWindow())
+
# Get voxels as a numpy array
shortcut.setKey(qt.QKeySequence('Ctrl+Shift+s'))
+
voxels = slicer.util.arrayFromVolume(volumeNode)
shortcut.connect( 'activated()', exportLabelmap)
+
print voxels.shape
 
</pre>
 
</pre>
  
=== Export model nodes from segmentation node ===
+
==Combine multiple volumes into one==
 +
 
 +
This example combines two volumes into a new one by subtracting one from the other.
  
 
<pre>
 
<pre>
seg = getNode('Segmentation')
+
import SampleData
exportedModelsNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLModelHierarchyNode')
+
[input1Volume, input2Volume] = SampleData.SampleDataLogic().downloadDentalSurgery()
slicer.modules.segmentations.logic().ExportAllSegmentsToModelHierarchy(seg, exportedModelsNode)
+
 
 +
import slicer.util
 +
a = slicer.util.arrayFromVolume(input1Volume)
 +
b = slicer.util.arrayFromVolume(input2Volume)
 +
 
 +
# 'a' and 'b' are numpy arrays,
 +
# they can be combined using any numpy array operations
 +
# to produce the result array 'c'
 +
c = b-a
 +
 
 +
volumeNode = slicer.modules.volumes.logic().CloneVolume(input1Volume, "Difference")
 +
slicer.util.updateVolumeFromArray(volumeNode, c)
 +
setSliceViewerLayers(background=volumeNode)
 
</pre>
 
</pre>
  
=== Show a segmentation in 3D ===
+
==Thick slab reconstruction and maximum/minimum intensity volume projections==
Segmentation can only be shown in 3D if closed surface representation (or other 3D-displayable representation) is available. To create closed surface representation:
+
 
 +
Set up 'red' slice viewer to show thick slab reconstructed from 3 slices:
 
<pre>
 
<pre>
segmentation.CreateClosedSurfaceRepresentation()
+
sliceNode = slicer.mrmlScene.GetNodeByID('vtkMRMLSliceNodeRed')
</pre>
+
appLogic = slicer.app.applicationLogic()
 +
sliceLogic = appLogic.GetSliceLogic(sliceNode)
 +
sliceLayerLogic = sliceLogic.GetBackgroundLayer()
 +
reslice = sliceLayerLogic.GetReslice()
 +
reslice.SetSlabModeToMean()
 +
reslice.SetSlabNumberOfSlices(10) # mean of 10 slices will computed
 +
reslice.SetSlabSliceSpacingFraction(0.3) # spacing between each slice is 0.3 pixel (total 10 * 0.3 = 3 pixel neighborhood)
 +
sliceNode.Modified()
 +
</pre>
  
=== Get a representation of a segment ===
+
Set up 'red' slice viewer to show maximum intensity projection (MIP):
Access binary labelmap stored in a segmentation node (without exporting it to a volume node) - if it does not exist, it will return None:
 
 
<pre>
 
<pre>
image = segmentationNode.GetBinaryLabelmapRepresentation(segmentID)
+
sliceNode = slicer.mrmlScene.GetNodeByID('vtkMRMLSliceNodeRed')
 +
appLogic = slicer.app.applicationLogic()
 +
sliceLogic = appLogic.GetSliceLogic(sliceNode)
 +
sliceLayerLogic = sliceLogic.GetBackgroundLayer()
 +
reslice = sliceLayerLogic.GetReslice()
 +
reslice.SetSlabModeToMax()
 +
reslice.SetSlabNumberOfSlices(600) # use a large number of slices (600) to cover the entire volume
 +
reslice.SetSlabSliceSpacingFraction(0.5) # spacing between slices are 0.5 pixel (supersampling is useful to reduce interpolation artifacts)
 +
sliceNode.Modified()
 
</pre>
 
</pre>
Get closed surface, if it does not exist, it will return None:
+
 
 +
The projected image is available in a ''vtkImageData'' object by calling ''reslice.GetOutput()''.
 +
 
 +
==Change default file type for nodes (that have never been saved yet)==
 +
Default node can be specified that will be used as a basis of all new storage nodes. This can be used for setting default file extension. For example, change file format to STL for model nodes:
 
<pre>
 
<pre>
polydata = segmentationNode.GetClosedSurfaceRepresentation(segmentID)
+
defaultModelStorageNode = slicer.vtkMRMLModelStorageNode()
</pre>
+
defaultModelStorageNode.SetDefaultWriteFileExtension('stl')
Get binary labelmap representation. If it does not exist then it will be created for that single segment. Applies parent transforms by default (if not desired, another argument needs to be added to the end: false):
+
slicer.mrmlScene.AddDefaultNode(defaultModelStorageNode)
<pre>
 
import vtkSegmentationCorePython as vtkSegmentationCore
 
outputOrientedImageData = vtkSegmentationCore.vtkOrientedImageData()
 
slicer.vtkSlicerSegmentationsModuleLogic.GetSegmentBinaryLabelmapRepresentation(segmentationNode, segmentID, outputOrientedImageData)
 
</pre>
 
Same as above, for closed surface representation:
 
<pre>
 
outputPolyData = vtk.vtkPolyData()
 
slicer.vtkSlicerSegmentationsModuleLogic.GetSegmentClosedSurfaceRepresentation(segmentationNode, segmentID, outputPolyData)
 
 
</pre>
 
</pre>
  
=== Convert all segments using default path and conversion parameters ===
+
To permanently change default file extension on your computer, copy-paste the code above into your application startup script (you can find its location in menu: Edit / Application settings / General / Application startup script).
 +
 
 +
==Change file type for saving for all volumes (with already existing storage nodes)==
 +
 
 +
If it is not necessary to preserve file paths then the simplest is to configure default storage node (as shown in the example above), then delete all existing storage nodes. When save dialog is opened, default storage nodes will be recreated.
 +
 
 
<pre>
 
<pre>
segmentationNode.CreateBinaryLabelmapRepresentation()
+
# Delete existing model storage nodes so that they will be recreated with default settings
 +
existingModelStorageNodes = slicer.util.getNodesByClass('vtkMRMLModelStorageNode')
 +
for modelStorageNode in existingModelStorageNodes:
 +
  slicer.mrmlScene.RemoveNode(modelStorageNode)
 
</pre>
 
</pre>
  
=== Convert all segments using custom path or conversion parameters ===
+
To update existing storage nodes to use new file extension (but keep all other parameters unchanged) you can use this approach (example is for volume storage):
Change reference image geometry parameter based on an existing referenceImageData image:
 
<pre>
 
import vtkSegmentationCorePython as vtkSegmentationCore
 
referenceGeometry = vtkSegmentationCore.vtkSegmentationConverter.SerializeImageGeometry(referenceImageData)
 
segmentation.SetConversionParameter(vtkSegmentationCore.vtkSegmentationConverter.GetReferenceImageGeometryParameterName(), referenceGeometry)
 
</pre>
 
  
=== Re-convert using a modified conversion parameter ===
 
Changing smoothing factor for closed surface generation:
 
 
<pre>
 
<pre>
import vtkSegmentationCorePython as vtkSegmentationCore
+
requiredFileExtension = '.nia'
segmentation = getNode('Segmentation').GetSegmentation()
+
originalFileExtension = '.nrrd'
 
+
volumeNodes = slicer.util.getNodesByClass('vtkMRMLScalarVolumeNode')
# Turn of surface smoothing
+
for volumeNode in volumeNodes:
segmentation.SetConversionParameter('Smoothing factor','0.0')
+
  volumeStorageNode = volumeNode.GetStorageNode()
 
+
  if not volumeStorageNode:
# Recreate representation using modified parameters (and default conversion path)
+
    volumeNode.AddDefaultStorageNode()
segmentation.RemoveRepresentation(vtkSegmentationCore.vtkSegmentationConverter.GetSegmentationClosedSurfaceRepresentationName())
+
    volumeStorageNode = volumeNode.GetStorageNode()
segmentation.CreateRepresentation(vtkSegmentationCore.vtkSegmentationConverter.GetSegmentationClosedSurfaceRepresentationName())
+
    volumeStorageNode.SetFileName(volumeNode.GetName()+requiredFileExtension)
 +
  else:
 +
    volumeStorageNode.SetFileName(volumeStorageNode.GetFileName().replace(originalFileExtension, requiredFileExtension))
 
</pre>
 
</pre>
  
=== Get centroid of a segment in world (RAS) coordinates ===
+
To set all volume nodes to save uncompressed by default (add this to .slicerrc.py so it takes effect for the whole session):
 
 
This example shows how to get centroid of a segment in world coordinates and show that position in all slice views.
 
 
 
 
<pre>
 
<pre>
segmentationNode = getNode('Segmentation')
+
#set the default volume storage to not compress by default
segmentId = 'Segment_1'
+
defaultVolumeStorageNode = slicer.vtkMRMLVolumeArchetypeStorageNode()
 +
defaultVolumeStorageNode.SetUseCompression(0)
 +
slicer.mrmlScene.AddDefaultNode(defaultVolumeStorageNode)
 +
logging.info("Volume nodes will be stored uncompressed by default")
 +
</pre>
  
# Get array voxel coordinates
+
Same thing as above, but applied to all  segmentations instead of volumes:
import numpy as np
+
<pre>
seg=arrayFromSegment(segmentation_node, segmentId)
+
#set the default volume storage to not compress by default
# numpy array has voxel coordinates in reverse order (KJI instead of IJK)
+
defaultVolumeStorageNode = slicer.vtkMRMLSegmentationStorageNode()
# and the array is cropped to minimum size in the segmentation
+
defaultVolumeStorageNode.SetUseCompression(0)
mean_KjiCropped = [coords.mean() for coords in np.nonzero(seg)]
+
slicer.mrmlScene.AddDefaultNode(defaultVolumeStorageNode)
 +
logging.info("Segmentation nodes will be stored uncompressed
 +
</pre>
  
# Get segmentation voxel coordinates
+
==Sequences==
segImage = segmentationNode.GetBinaryLabelmapRepresentation(segmentId)
 
segImageExtent = segImage.GetExtent()
 
# origin of the array in voxel coordinates is determined by the start extent
 
mean_Ijk = [mean_KjiCropped[2], mean_KjiCropped[1], mean_KjiCropped[0]] + np.array([segImageExtent[0], segImageExtent[2], segImageExtent[4]])
 
  
# Get segmentation physical coordinates
+
===Concatenate all sequences in the scene into a new sequence===
ijkToWorld = vtk.vtkMatrix4x4()
 
segImage.GetImageToWorldMatrix(ijkToWorld)
 
mean_World = [0, 0, 0, 1]
 
ijkToRas.MultiplyPoint(np.append(mean_Ijk,1.0), mean_World)
 
mean_World = mean_World[0:3]
 
  
# If segmentation node is transformed, apply that transform to get RAS coordinates
+
<pre>
transformWorldToRas = vtk.vtkGeneralTransform()
+
# Get all sequence nodes in the scene
slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(segmentationNode.GetParentTransformNode(), None, transformWorldToRas)
+
sequenceNodes = slicer.util.getNodesByClass('vtkMRMLSequenceNode')
mean_Ras = transformWorldToRas.TransformPoint(mean_World)
+
mergedSequenceNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSequenceNode', 'Merged sequence')
 +
 
 +
# Merge all sequence nodes into a new sequence node
 +
mergedIndexValue = 0
 +
for sequenceNode in sequenceNodes:
 +
    for itemIndex in range(sequenceNode.GetNumberOfDataNodes()):
 +
        dataNode = sequenceNode.GetNthDataNode(itemIndex)
 +
        mergedSequenceNode.SetDataNodeAtValue(dataNode, str(mergedIndexValue))
 +
        mergedIndexValue += 1
 +
    # Delete the sequence node we copied the data from, to prevent sharing of the same
 +
    # node by multiple sequences
 +
    slicer.mrmlScene.RemoveNode(sequenceNode)
  
# Show mean position value and jump to it in all slice viewers
+
# Create a sequence browser node for the new merged sequence
print(mean_Ras)
+
mergedSequenceBrowserNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSequenceBrowserNode', 'Merged')
slicer.modules.markups.logic().JumpSlicesToLocation(mean_Ras[0], mean_Ras[1], mean_Ras[2], True)
+
mergedSequenceBrowserNode.AddSynchronizedSequenceNode(mergedSequenceNode)
 +
slicer.modules.sequencebrowser.setToolBarActiveBrowserNode(mergedSequenceBrowserNode)
 +
# Show proxy node in slice viewers
 +
mergedProxyNode = mergedSequenceBrowserNode.GetProxyNode(mergedSequenceNode)
 +
slicer.util.setSliceViewerLayers(background=mergedProxyNode)
 
</pre>
 
</pre>
  
=== How to run segment editor effects from a script ===
+
==Segmentations==
  
Editor effects are complex because they need to handle changing master volumes, undo/redo, masking operations, etc. Therefore, instead of using a segment editor effect, it is simpler to run the underlying filters directly from script.
+
===Create a segmentation from a labelmap volume and display in 3D===
  
This example demonstrates how to use Segment editor effects (without GUI, using qMRMLSegmentEditorWidget):
+
<pre>
 +
labelmapVolumeNode = getNode('label')
 +
seg = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSegmentationNode')
 +
slicer.modules.segmentations.logic().ImportLabelmapToSegmentationNode(labelmapVolumeNode, seg)
 +
seg.CreateClosedSurfaceRepresentation()
 +
slicer.mrmlScene.RemoveNode(labelmapVolumeNode)
 +
</pre>
 +
 
 +
The last line is optional. It removes the original labelmap volume so that the same information is not shown twice.
  
* [https://gist.github.com/lassoan/2d5a5b73645f65a5eb6f8d5f97abf31b brain tumor segmentation using grow from seeds effect]
+
===Export labelmap node from segmentation node===
* [https://gist.github.com/lassoan/1673b25d8e7913cbc245b4f09ed853f9 skin surface extraction using thresholding and smoothing]
 
* [https://gist.github.com/lassoan/2f5071c562108dac8efe277c78f2620f mask a volume with segments and compute histogram for each region]
 
* [https://gist.github.com/lassoan/5ad51c89521d3cd9c5faf65767506b37 create fat/muscle/bone segment by thresholding and report volume of each segment]
 
  
This example shows how to perform operations on segmentations using VTK filters:
+
Export smallest possible labelmap:
* [https://gist.github.com/lassoan/7c94c334653010696b2bf96abc0ac8e7 brain tumor segmentation using grow from seeds effect]
 
  
== Accessing views, renderers, and cameras ==
+
<pre>
 +
seg = getNode('Segmentation')
 +
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')
 +
slicer.modules.segmentations.logic().ExportAllSegmentsToLabelmapNode(seg, labelmapVolumeNode)
 +
</pre>
  
Iterate through all 3D views in current layout:
+
Export labelmap that matches geometry of a chosen reference volume:
  
 
<pre>
 
<pre>
layoutManager = slicer.app.layoutManager()
+
seg = getNode('Segmentation')
for threeDViewIndex in range(layoutManager.threeDViewCount) :
+
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')
  view = layoutManager.threeDWidget(threeDViewIndex).threeDView()
+
slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, referenceVolumeNode)
  threeDViewNode = view.mrmlViewNode()
 
  cameraNode = slicer.modules.cameras.logic().GetViewActiveCameraNode(threeDViewNode)
 
  print('View node for 3D widget ' + str(threeDViewIndex))
 
  print('  Name: ' + threeDViewNode .GetName())
 
  print('  ID: ' + threeDViewNode .GetID())
 
  print('  Camera ID: ' + cameraNode.GetID())
 
 
</pre>
 
</pre>
  
Iterate through all slice views in current layout:
+
Export by pressing Ctrl+Shift+s key:
  
 
<pre>
 
<pre>
layoutManager = slicer.app.layoutManager()
+
outputPath = "c:/tmp"
for sliceViewName in layoutManager.sliceViewNames():
+
 
  view = layoutManager.sliceWidget(sliceViewName).sliceView()
+
def exportLabelmap():
  sliceNode = view.mrmlSliceNode()
+
    segmentationNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLSegmentationNode")
  sliceLogic = slicer.app.applicationLogic().GetSliceLogic(sliceNode)
+
    referenceVolumeNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLScalarVolumeNode")
  compositeNode = sliceLogic.GetSliceCompositeNode()
+
    labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')
  print('Slice view ' + str(sliceViewName))
+
    slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, referenceVolumeNode)
  print('  Name: ' + sliceNode.GetName())
+
    filepath = outputPath + "/" + referenceVolumeNode.GetName()+"-label.nrrd"
  print('  ID: ' + sliceNode.GetID())
+
    slicer.util.saveNode(labelmapVolumeNode, filepath)
  print('  Background volume: {0}'.format(compositeNode.GetBackgroundVolumeID()))
+
    slicer.mrmlScene.RemoveNode(labelmapVolumeNode.GetDisplayNode().GetColorNode())
  print('  Foreground volume: {0} (opacity: {1})'.format(compositeNode.GetForegroundVolumeID(), compositeNode.GetForegroundOpacity()))
+
    slicer.mrmlScene.RemoveNode(labelmapVolumeNode)
  print(' Label volume: {0} (opacity: {1})'.format(compositeNode.GetLabelVolumeID(), compositeNode.GetLabelOpacity()))
+
    slicer.util.delayDisplay("Segmentation saved to "+filepath)
 +
 
 +
shortcut = qt.QShortcut(slicer.util.mainWindow())
 +
shortcut.setKey(qt.QKeySequence('Ctrl+Shift+s'))
 +
shortcut.connect( 'activated()', exportLabelmap)
 
</pre>
 
</pre>
  
For low-level manipulation of views, it is possible to access VTK render windows, renderers and cameras of views in the current layout.
+
===Export model nodes from segmentation node===
 +
 
 
<pre>
 
<pre>
renderWindow = view.renderWindow()
+
segmentationNode = getNode("Segmentation")
renderers = renderWindow.GetRenderers()
+
shNode = slicer.mrmlScene.GetSubjectHierarchyNode()
renderer = renderers.GetItemAsObject(0)
+
exportFolderItemId = shNode.CreateFolderItem(shNode.GetSceneItemID(), "Segments")
camera = cameraNode.GetCamera()
+
slicer.modules.segmentations.logic().ExportAllSegmentsToModels(segmentationNode, exportFolderItemId)
 
</pre>
 
</pre>
  
== Hide view controller bars ==
+
===Show a segmentation in 3D===
 +
Segmentation can only be shown in 3D if closed surface representation (or other 3D-displayable representation) is available. To create closed surface representation:
 +
<pre>
 +
segmentation.CreateClosedSurfaceRepresentation()
 +
</pre>
  
 +
===Get a representation of a segment===
 +
Access binary labelmap stored in a segmentation node (without exporting it to a volume node) - if it does not exist, it will return None:
 +
<pre>
 +
image = segmentationNode.GetBinaryLabelmapRepresentation(segmentID)
 +
</pre>
 +
Get closed surface, if it does not exist, it will return None:
 +
<pre>
 +
polydata = segmentationNode.GetClosedSurfaceRepresentation(segmentID)
 +
</pre>
 +
Get binary labelmap representation. If it does not exist then it will be created for that single segment. Applies parent transforms by default (if not desired, another argument needs to be added to the end: false):
 
<pre>
 
<pre>
slicer.app.layoutManager().threeDWidget(0).threeDController().setVisible(False)
+
import vtkSegmentationCorePython as vtkSegmentationCore
slicer.app.layoutManager().sliceWidget('Red').sliceController().setVisible(False)
+
outputOrientedImageData = vtkSegmentationCore.vtkOrientedImageData()
slicer.app.layoutManager().plotWidget(0).plotController().setVisible(False)
+
slicer.vtkSlicerSegmentationsModuleLogic.GetSegmentBinaryLabelmapRepresentation(segmentationNode, segmentID, outputOrientedImageData)
slicer.app.layoutManager().tableWidget(0).tableController().setVisible(False)
 
 
</pre>
 
</pre>
 
+
Same as above, for closed surface representation:
== Change 3D view background color ==
 
 
 
 
<pre>
 
<pre>
renderWindow = slicer.app.layoutManager().threeDWidget(0).threeDView().renderWindow()
+
outputPolyData = vtk.vtkPolyData()
renderer = renderWindow.GetRenderers().GetFirstRenderer()
+
slicer.vtkSlicerSegmentationsModuleLogic.GetSegmentClosedSurfaceRepresentation(segmentationNode, segmentID, outputPolyData)
renderer.SetBackground(1,0,0)
 
renderer.SetBackground2(1,0,0)
 
renderWindow.Render()
 
 
</pre>
 
</pre>
  
== Subject hierarchy ==  
+
===Convert all segments using default path and conversion parameters===
==== Get the pseudo-singleton subject hierarchy node ====
+
<pre>
It manages the whole hierarchy and provides functions to access and manipulate
+
segmentationNode.CreateBinaryLabelmapRepresentation()
  shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)
+
</pre>
 +
 
 +
===Convert all segments using custom path or conversion parameters===
 +
Change reference image geometry parameter based on an existing referenceImageData image:
 +
<pre>
 +
import vtkSegmentationCorePython as vtkSegmentationCore
 +
referenceGeometry = vtkSegmentationCore.vtkSegmentationConverter.SerializeImageGeometry(referenceImageData)
 +
segmentation.SetConversionParameter(vtkSegmentationCore.vtkSegmentationConverter.GetReferenceImageGeometryParameterName(), referenceGeometry)
 +
</pre>
  
==== Create subject hierarchy item ====
+
===Re-convert using a modified conversion parameter===
  # If it is for a data node, it is automatically created, but the create function can be used to set parent:
+
Changing smoothing factor for closed surface generation:
  shNode.CreateItem(parentItemID, dataNode)
+
<pre>
  # If it is a hierarchy item without a data node, then the create function must be used:
+
import vtkSegmentationCorePython as vtkSegmentationCore
  shNode.CreateSubjectItem(parentItemID, name)
+
segmentation = getNode('Segmentation').GetSegmentation()
  shNode.CreateFolderItem(parentItemID, name)
 
  shNode.CreateHierarchyItem(parentItemID, name, level) # Advanced method to set level attribute manually (usually subject, study, or folder, but it can be a virtual branch for example)
 
  
==== Get subject hierarchy item ====
+
# Turn of surface smoothing
Items in subject hierarchy are uniquely identified by integer IDs
+
segmentation.SetConversionParameter('Smoothing factor','0.0')
  # Get scene item ID first because it is the root item:
 
  sceneItemID = shNode.GetSceneItemID()
 
  # Get direct child by name
 
  subjectItemID = shNode.GetItemChildWithName(sceneItemID, 'Subject_1')
 
  # Get item for data node
 
  itemID = shNode.GetItemByDataNode(dataNode)
 
  # Get item by UID (such as DICOM)
 
  itemID = shNode.GetItemByUID(slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMUIDName(), seriesInstanceUid)
 
  itemID = shNode.GetItemByUIDList(slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMInstanceUIDName(), instanceUID)
 
  # Invalid item ID for checking validity of a given ID (most functions return the invalid ID when item is not found)
 
  invalidItemID = slicer.vtkMRMLSubjectHierarchyNode.GetInvalidItemID()
 
  
==== Traverse children of a subject hierarchy item ====
+
# Recreate representation using modified parameters (and default conversion path)
  children = vtk.vtkIdList()
+
segmentation.RemoveRepresentation(vtkSegmentationCore.vtkSegmentationConverter.GetSegmentationClosedSurfaceRepresentationName())
  shNode.GetItemChildren(parent, children)
+
segmentation.CreateRepresentation(vtkSegmentationCore.vtkSegmentationConverter.GetSegmentationClosedSurfaceRepresentationName())
  for i in range(children.GetNumberOfIds()):
+
</pre>
    child = children.GetId(i)
 
    ...
 
  
==== Manipulate subject hierarchy item ====
+
===Get centroid of a segment in world (RAS) coordinates===
Instead of node operations on the individual subject hierarchy nodes, item operations are performed on the one subject hierarchy node.
 
  # Set item name
 
  shNode.SetItemName(itemID, 'NewName')
 
  # Set item parent (reparent)
 
  shNode.SetItemParent(itemID, newParentItemID)
 
  # Set visibility of data nodes associated to items in a branch (or a leaf item)
 
  shNode.SetDisplayVisibilityForBranch(itemID, 1)
 
  
==== Filter items in TreeView or ComboBox ====
+
This example shows how to get centroid of a segment in world coordinates and show that position in all slice views.
Displayed items can be filtered using ''setAttributeFilter'' method. An example of the usage can be found in the  [https://github.com/Slicer/Slicer/blob/e66e3b08e35384526528e6ae678e9ec9f079f286/Applications/SlicerApp/Testing/Python/SubjectHierarchyGenericSelfTest.py#L352-L360 unit test]. Modified version here:
 
    print(shTreeView.displayedItemCount()) # 5
 
    shTreeView.setAttributeFilter('DICOM.Modality') # Nodes must have this attribute
 
    print(shTreeView.displayedItemCount()) # 3
 
    shTreeView.setAttributeFilter('DICOM.Modality','CT') # Have attribute and equal 'CT'
 
    print(shTreeView.displayedItemCount()) # 1
 
    shTreeView.removeAttributeFilter()
 
    print(shTreeView.displayedItemCount()) # 5
 
  
=== Listen to subject hierarchy item events ===
+
<pre>
The subject hierarchy node sends the node item id as calldata. Item IDs are vtkIdType, which are NOT vtkObjects. You need to use vtk.calldata_type(vtk.VTK_LONG) (otherwise the application crashes).
+
segmentationNode = getNode('Segmentation')
 
+
segmentId = 'Segment_1'
  class MyListenerClass(VTKObservationMixin):
 
    def __init__(self):
 
      VTKObservationMixin.__init__(self)
 
     
 
      shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)
 
      self.addObserver(shNode, shNode.SubjectHierarchyItemModifiedEvent, self.shItemModifiedEvent)
 
   
 
    @vtk.calldata_type(vtk.VTK_LONG)
 
    def shItemModifiedEvent(self, caller, eventId, callData):
 
      print("SH Node modified")
 
      print("SH item ID: {0}".format(callData))
 
  
== Plotting ==
+
# Get array voxel coordinates
 +
import numpy as np
 +
seg=arrayFromSegment(segmentation_node, segmentId)
 +
# numpy array has voxel coordinates in reverse order (KJI instead of IJK)
 +
# and the array is cropped to minimum size in the segmentation
 +
mean_KjiCropped = [coords.mean() for coords in np.nonzero(seg)]
  
=== Create histogram plot of a volume ===
+
# Get segmentation voxel coordinates
 +
segImage = segmentationNode.GetBinaryLabelmapRepresentation(segmentId)
 +
segImageExtent = segImage.GetExtent()
 +
# origin of the array in voxel coordinates is determined by the start extent
 +
mean_Ijk = [mean_KjiCropped[2], mean_KjiCropped[1], mean_KjiCropped[0]] + np.array([segImageExtent[0], segImageExtent[2], segImageExtent[4]])
  
<pre>
+
# Get segmentation physical coordinates
# Get a volume from SampleData
+
ijkToWorld = vtk.vtkMatrix4x4()
import SampleData
+
segImage.GetImageToWorldMatrix(ijkToWorld)
volumeNode = SampleData.SampleDataLogic().downloadMRHead()
+
mean_World = [0, 0, 0, 1]
 +
ijkToRas.MultiplyPoint(np.append(mean_Ijk,1.0), mean_World)
 +
mean_World = mean_World[0:3]
 +
 
 +
# If segmentation node is transformed, apply that transform to get RAS coordinates
 +
transformWorldToRas = vtk.vtkGeneralTransform()
 +
slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(segmentationNode.GetParentTransformNode(), None, transformWorldToRas)
 +
mean_Ras = transformWorldToRas.TransformPoint(mean_World)
 +
 
 +
# Show mean position value and jump to it in all slice viewers
 +
print(mean_Ras)
 +
slicer.modules.markups.logic().JumpSlicesToLocation(mean_Ras[0], mean_Ras[1], mean_Ras[2], True)
 +
</pre>
 +
 
 +
===How to run segment editor effects from a script===
  
# Compute histogram values
+
Editor effects are complex because they need to handle changing master volumes, undo/redo, masking operations, etc. Therefore, instead of using a segment editor effect, it is simpler to run the underlying filters directly from script.
import numpy as np
 
histogram = np.histogram(arrayFromVolume(volumeNode), bins=50)
 
  
# Save results to a new table node
+
This example demonstrates how to use Segment editor effects (without GUI, using qMRMLSegmentEditorWidget):
tableNode=slicer.mrmlScene.AddNewNodeByClass("vtkMRMLTableNode")
 
updateTableFromArray(tableNode, histogram)
 
tableNode.GetTable().GetColumn(0).SetName("Count")
 
tableNode.GetTable().GetColumn(1).SetName("Intensity")
 
  
# Create plot
+
*[https://gist.github.com/lassoan/2d5a5b73645f65a5eb6f8d5f97abf31b brain tumor segmentation using grow from seeds effect]
plotSeriesNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLPlotSeriesNode", volumeNode.GetName() + ' histogram')
+
*[https://gist.github.com/lassoan/ef30bc27a22a648ead7f82243f5cc7d5 AI-assisted brain tumor segmentation]
plotSeriesNode.SetAndObserveTableNodeID(tableNode.GetID())
+
*[https://gist.github.com/lassoan/1673b25d8e7913cbc245b4f09ed853f9 skin surface extraction using thresholding and smoothing]
plotSeriesNode.SetXColumnName("Intensity")
+
*[https://gist.github.com/lassoan/2f5071c562108dac8efe277c78f2620f mask a volume with segments and compute histogram for each region]
plotSeriesNode.SetYColumnName("Count")
+
*[https://gist.github.com/lassoan/5ad51c89521d3cd9c5faf65767506b37 create fat/muscle/bone segment by thresholding and report volume of each segment]
plotSeriesNode.SetPlotType(plotSeriesNode.PlotTypeScatterBar)
+
 
 +
This example shows how to perform operations on segmentations using VTK filters:
 +
 
 +
*[https://gist.github.com/lassoan/7c94c334653010696b2bf96abc0ac8e7 brain tumor segmentation using grow from seeds effect]
 +
 
 +
==Quantifying segments==
 +
 
 +
===Get size, position, and orientation of each segment===
 +
 
 +
This example computes oriented bounding box for each segment and displays them using annotation ROI.
 +
 
 +
<pre>
 +
segmentationNode = getNode('Segmentation')
 +
 
 +
# Compute bounding boxes
 +
import SegmentStatistics
 +
segStatLogic = SegmentStatistics.SegmentStatisticsLogic()
 +
segStatLogic.getParameterNode().SetParameter("Segmentation", segmentationNode.GetID())
 +
segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_origin_ras.enabled",str(True))
 +
segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_diameter_mm.enabled",str(True))
 +
segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_direction_ras_x.enabled",str(True))
 +
segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_direction_ras_y.enabled",str(True))
 +
segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_direction_ras_z.enabled",str(True))
 +
segStatLogic.computeStatistics()
 +
stats = segStatLogic.getStatistics()
 +
 
 +
# Draw ROI for each oriented bounding box
 +
import numpy as np
 +
for segmentId in stats['SegmentIDs']:
 +
    # Get bounding box
 +
    obb_origin_ras = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_origin_ras"])
 +
    obb_diameter_mm = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_diameter_mm"])
 +
    obb_direction_ras_x = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_direction_ras_x"])
 +
    obb_direction_ras_y = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_direction_ras_y"])
 +
    obb_direction_ras_z = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_direction_ras_z"])
 +
    # Create ROI
 +
    segment = segmentationNode.GetSegmentation().GetSegment(segmentId)
 +
    roi=slicer.mrmlScene.AddNewNodeByClass("vtkMRMLAnnotationROINode")
 +
    roi.SetName(segment.GetName()+' bounding box')
 +
    roi.SetXYZ(0.0, 0.0, 0.0)
 +
    roi.SetRadiusXYZ(*(0.5*obb_diameter_mm))
 +
    # Position and orient ROI using a transform
 +
    obb_center_ras = obb_origin_ras+0.5*(obb_diameter_mm[0] * obb_direction_ras_x + obb_diameter_mm[1] * obb_direction_ras_y + obb_diameter_mm[2] * obb_direction_ras_z)
 +
    boundingBoxToRasTransform = np.row_stack((np.column_stack((obb_direction_ras_x, obb_direction_ras_y, obb_direction_ras_z, obb_center_ras)), (0, 0, 0, 1)))
 +
    boundingBoxToRasTransformMatrix = slicer.util.vtkMatrixFromArray(boundingBoxToRasTransform)
 +
    transformNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLTransformNode')
 +
    transformNode.SetAndObserveMatrixTransformToParent(boundingBoxToRasTransformMatrix)
 +
    roi.SetAndObserveTransformNodeID(transformNode.GetID())
 +
</pre>
 +
 
 +
==Accessing views, renderers, and cameras==
 +
 
 +
Iterate through all 3D views in current layout:
 +
 
 +
<pre>
 +
layoutManager = slicer.app.layoutManager()
 +
for threeDViewIndex in range(layoutManager.threeDViewCount) :
 +
  view = layoutManager.threeDWidget(threeDViewIndex).threeDView()
 +
  threeDViewNode = view.mrmlViewNode()
 +
  cameraNode = slicer.modules.cameras.logic().GetViewActiveCameraNode(threeDViewNode)
 +
  print('View node for 3D widget ' + str(threeDViewIndex))
 +
  print('  Name: ' + threeDViewNode .GetName())
 +
  print('  ID: ' + threeDViewNode .GetID())
 +
  print('  Camera ID: ' + cameraNode.GetID())
 +
</pre>
 +
 
 +
Iterate through all slice views in current layout:
 +
 
 +
<pre>
 +
layoutManager = slicer.app.layoutManager()
 +
for sliceViewName in layoutManager.sliceViewNames():
 +
  view = layoutManager.sliceWidget(sliceViewName).sliceView()
 +
  sliceNode = view.mrmlSliceNode()
 +
  sliceLogic = slicer.app.applicationLogic().GetSliceLogic(sliceNode)
 +
  compositeNode = sliceLogic.GetSliceCompositeNode()
 +
  print('Slice view ' + str(sliceViewName))
 +
  print('  Name: ' + sliceNode.GetName())
 +
  print('  ID: ' + sliceNode.GetID())
 +
  print('  Background volume: {0}'.format(compositeNode.GetBackgroundVolumeID()))
 +
  print('  Foreground volume: {0} (opacity: {1})'.format(compositeNode.GetForegroundVolumeID(), compositeNode.GetForegroundOpacity()))
 +
  print('  Label volume: {0} (opacity: {1})'.format(compositeNode.GetLabelVolumeID(), compositeNode.GetLabelOpacity()))
 +
</pre>
 +
 
 +
For low-level manipulation of views, it is possible to access VTK render windows, renderers and cameras of views in the current layout.
 +
<pre>
 +
renderWindow = view.renderWindow()
 +
renderers = renderWindow.GetRenderers()
 +
renderer = renderers.GetItemAsObject(0)
 +
camera = cameraNode.GetCamera()
 +
</pre>
 +
 
 +
==Hide view controller bars==
 +
 
 +
<pre>
 +
slicer.app.layoutManager().threeDWidget(0).threeDController().setVisible(False)
 +
slicer.app.layoutManager().sliceWidget('Red').sliceController().setVisible(False)
 +
slicer.app.layoutManager().plotWidget(0).plotController().setVisible(False)
 +
slicer.app.layoutManager().tableWidget(0).tableController().setVisible(False)
 +
</pre>
 +
 
 +
==Customize widgets in view controller bars==
 +
 
 +
<pre>
 +
sliceController = slicer.app.layoutManager().sliceWidget("Red").sliceController()
 +
 
 +
# hide what is not needed
 +
sliceController.pinButton().hide()
 +
#sliceController.viewLabel().hide()
 +
sliceController.fitToWindowToolButton().hide()
 +
sliceController.sliceOffsetSlider().hide()
 +
 
 +
# add custom widgets
 +
myButton = qt.QPushButton("My custom button")
 +
sliceController.barLayout().addWidget(b)
 +
</pre>
 +
 
 +
==Change 3D view background color==
 +
 
 +
<pre>
 +
viewNode = slicer.app.layoutManager().threeDWidget(0).mrmlViewNode()
 +
viewNode.SetBackgroundColor(1,0,0)
 +
viewNode.SetBackgroundColor2(1,0,0)
 +
 
 +
</pre>
 +
 
 +
==Hide Slicer logo from main window (to increase space)==
 +
 
 +
<pre>
 +
slicer.util.findChild(slicer.util.mainWindow(), 'LogoLabel').visible = False
 +
</pre>
 +
 
 +
==Subject hierarchy==
 +
====Get the pseudo-singleton subject hierarchy node====
 +
It manages the whole hierarchy and provides functions to access and manipulate
 +
  shNode = slicer.mrmlScene.GetSubjectHierarchyNode()
 +
 
 +
====Create subject hierarchy item====
 +
  # If it is for a data node, it is automatically created, but the create function can be used to set parent:
 +
  shNode.CreateItem(parentItemID, dataNode)
 +
  # If it is a hierarchy item without a data node, then the create function must be used:
 +
  shNode.CreateSubjectItem(parentItemID, name)
 +
  shNode.CreateFolderItem(parentItemID, name)
 +
  shNode.CreateHierarchyItem(parentItemID, name, level) # Advanced method to set level attribute manually (usually subject, study, or folder, but it can be a virtual branch for example)
 +
 
 +
====Get subject hierarchy item====
 +
Items in subject hierarchy are uniquely identified by integer IDs
 +
  # Get scene item ID first because it is the root item:
 +
  sceneItemID = shNode.GetSceneItemID()
 +
  # Get direct child by name
 +
  subjectItemID = shNode.GetItemChildWithName(sceneItemID, 'Subject_1')
 +
  # Get item for data node
 +
  itemID = shNode.GetItemByDataNode(dataNode)
 +
  # Get item by UID (such as DICOM)
 +
  itemID = shNode.GetItemByUID(slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMUIDName(), seriesInstanceUid)
 +
  itemID = shNode.GetItemByUIDList(slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMInstanceUIDName(), instanceUID)
 +
  # Invalid item ID for checking validity of a given ID (most functions return the invalid ID when item is not found)
 +
  invalidItemID = slicer.vtkMRMLSubjectHierarchyNode.GetInvalidItemID()
 +
 
 +
====Traverse children of a subject hierarchy item====
 +
  children = vtk.vtkIdList()
 +
  shNode.GetItemChildren(parent, children)
 +
  for i in range(children.GetNumberOfIds()):
 +
    child = children.GetId(i)
 +
    ...
 +
 
 +
====Manipulate subject hierarchy item====
 +
Instead of node operations on the individual subject hierarchy nodes, item operations are performed on the one subject hierarchy node.
 +
  # Set item name
 +
  shNode.SetItemName(itemID, 'NewName')
 +
  # Set item parent (reparent)
 +
  shNode.SetItemParent(itemID, newParentItemID)
 +
  # Set visibility of data node associated to an item
 +
  shNode.SetItemDisplayVisibility(itemID, 1)
 +
  # Set visibility of whole branch
 +
  # Note: Folder-type items (fodler, subject, study, etc.) create their own display nodes when show/hiding from UI.
 +
  #      The displayable managers use SH information to determine visibility of an item, so no need to show/hide individual leaf nodes any more.
 +
  #      Once the folder display node is created, it can be shown hidden simply using shNode.SetItemDisplayVisibility
 +
  # From python, this is how to trigger creating a folder display node
 +
  pluginHandler = slicer.qSlicerSubjectHierarchyPluginHandler().instance()
 +
  folderPlugin = pluginHandler.pluginByName('Folder')
 +
  folderPlugin.setDisplayVisibility(folderItemID, 1)
 +
 
 +
====Filter items in TreeView or ComboBox====
 +
Displayed items can be filtered using ''setAttributeFilter'' method. An example of the usage can be found in the  [https://github.com/Slicer/Slicer/blob/e66e3b08e35384526528e6ae678e9ec9f079f286/Applications/SlicerApp/Testing/Python/SubjectHierarchyGenericSelfTest.py#L352-L360 unit test]. Modified version here:
 +
    print(shTreeView.displayedItemCount()) # 5
 +
    shTreeView.setAttributeFilter('DICOM.Modality') # Nodes must have this attribute
 +
    print(shTreeView.displayedItemCount()) # 3
 +
    shTreeView.setAttributeFilter('DICOM.Modality','CT') # Have attribute and equal 'CT'
 +
    print(shTreeView.displayedItemCount()) # 1
 +
    shTreeView.removeAttributeFilter()
 +
    print(shTreeView.displayedItemCount()) # 5
 +
 
 +
===Listen to subject hierarchy item events===
 +
The subject hierarchy node sends the node item id as calldata. Item IDs are vtkIdType, which are NOT vtkObjects. You need to use vtk.calldata_type(vtk.VTK_LONG) (otherwise the application crashes).
 +
 
 +
  class MyListenerClass(VTKObservationMixin):
 +
    def __init__(self):
 +
      VTKObservationMixin.__init__(self)
 +
     
 +
      shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)
 +
      self.addObserver(shNode, shNode.SubjectHierarchyItemModifiedEvent, self.shItemModifiedEvent)
 +
   
 +
    @vtk.calldata_type(vtk.VTK_LONG)
 +
    def shItemModifiedEvent(self, caller, eventId, callData):
 +
      print("SH Node modified")
 +
      print("SH item ID: {0}".format(callData))
 +
 
 +
===Use whitelist to customize view menu===
 +
When right-clicking certain types of nodes in the 2D/3D views, a subject hierarchy menu pops up. If menu actions need to be removed, a whitelist can be used to specify the ones that should show up.
 +
  pluginHandler = slicer.qSlicerSubjectHierarchyPluginHandler.instance()
 +
  pluginLogic = pluginHandler.pluginLogic()
 +
  menuActions = pluginLogic.availableViewMenuActionNames()
 +
  # Returns ('RenamePointAction', 'DeletePointAction', 'ToggleSelectPointAction', 'EditPropertiesAction')
 +
  newActions = ['RenamePointAction']
 +
  pluginLogic.setDisplayedViewMenuActionNames(newActions)
 +
 
 +
==Plotting==
 +
 
 +
===Slicer plots displayed in view layout===
 +
 
 +
Create histogram plot of a volume and show it embedded in the view layout. More information: https://www.slicer.org/wiki/Documentation/Nightly/Developers/Plots
 +
 
 +
====Using <code>slicer.util.plot</code> utility function====
 +
 
 +
<pre>
 +
# Get a volume from SampleData and compute its histogram
 +
import SampleData
 +
import numpy as np
 +
volumeNode = SampleData.SampleDataLogic().downloadMRHead()
 +
histogram = np.histogram(arrayFromVolume(volumeNode), bins=50)
 +
 
 +
chartNode = slicer.util.plot(histogram, xColumnIndex = 1)
 +
chartNode.SetYAxisRangeAuto(False)
 +
chartNode.SetYAxisRange(0, 4e5)
 +
</pre>
 +
 
 +
[[Image:SlicerPlot.png]]
 +
 
 +
====Using MRML classes only====
 +
 
 +
<pre>
 +
# Get a volume from SampleData
 +
import SampleData
 +
volumeNode = SampleData.SampleDataLogic().downloadMRHead()
 +
 
 +
# Compute histogram values
 +
import numpy as np
 +
histogram = np.histogram(arrayFromVolume(volumeNode), bins=50)
 +
 
 +
# Save results to a new table node
 +
tableNode=slicer.mrmlScene.AddNewNodeByClass("vtkMRMLTableNode")
 +
updateTableFromArray(tableNode, histogram)
 +
tableNode.GetTable().GetColumn(0).SetName("Count")
 +
tableNode.GetTable().GetColumn(1).SetName("Intensity")
 +
 
 +
# Create plot
 +
plotSeriesNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLPlotSeriesNode", volumeNode.GetName() + ' histogram')
 +
plotSeriesNode.SetAndObserveTableNodeID(tableNode.GetID())
 +
plotSeriesNode.SetXColumnName("Intensity")
 +
plotSeriesNode.SetYColumnName("Count")
 +
plotSeriesNode.SetPlotType(plotSeriesNode.PlotTypeScatterBar)
 
plotSeriesNode.SetColor(0, 0.6, 1.0)
 
plotSeriesNode.SetColor(0, 0.6, 1.0)
  
# Create chart and add plot
+
# Create chart and add plot
plotChartNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLPlotChartNode")
+
plotChartNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLPlotChartNode")
plotChartNode.AddAndObservePlotSeriesNodeID(plotSeriesNode.GetID())
+
plotChartNode.AddAndObservePlotSeriesNodeID(plotSeriesNode.GetID())
plotChartNode.YAxisRangeAutoOff()
+
plotChartNode.YAxisRangeAutoOff()
plotChartNode.SetYAxisRange(0, 500000)
+
plotChartNode.SetYAxisRange(0, 500000)
 
+
 
# Show plot in layout
+
# Show plot in layout
slicer.modules.plots.logic().ShowChartInLayout(plotChartNode)
+
slicer.modules.plots.logic().ShowChartInLayout(plotChartNode)
 
+
</pre>
</pre>
+
 
 
+
===Using matplotlib===
== Execute external applications ==
+
 
 
+
Matplotlib may be used from within Slicer, but the default Tk backend locks up and crashes Slicer. However, Matplotlib may still be used through other backends. More details can be found on the [http://matplotlib.sourceforge.net/ MatPlotLib] pages.
How to run external applications from Slicer.
+
 
 
+
====Non-interactive plot====
=== Run process in default environment ===
+
 
 
+
<pre>
When a process is launched from Slicer then by default Slicer's ITK, VTK, Qt, etc. libraries are used. If an external application has its own version of these libraries, then the application is expected to crash. To prevent crashing, the application must be run in the environment where Slicer started up (without all Slicer-specific library paths). This startup environment can be retrieved using ''slicer.util.startupEnvironment()''.
+
try:
 +
  import matplotlib
 +
except ModuleNotFoundError:
 +
  pip_install('matplotlib')
 +
  import matplotlib
 +
 
 +
matplotlib.use('Agg')
 +
from pylab import *
 +
 
 +
t1 = arange(0.0, 5.0, 0.1)
 +
t2 = arange(0.0, 5.0, 0.02)
 +
t3 = arange(0.0, 2.0, 0.01)
 +
 
 +
subplot(211)
 +
plot(t1, cos(2*pi*t1)*exp(-t1), 'bo', t2, cos(2*pi*t2)*exp(-t2), 'k')
 +
grid(True)
 +
title('A tale of 2 subplots')
 +
ylabel('Damped')
 +
 
 +
subplot(212)
 +
plot(t3, cos(2*pi*t3), 'r--')
 +
grid(True)
 +
xlabel('time (s)')
 +
ylabel('Undamped')
 +
savefig('MatplotlibExample.png')
 +
 
 +
# Static image view
 +
pm = qt.QPixmap("MatplotlibExample.png")
 +
imageWidget = qt.QLabel()
 +
imageWidget.setPixmap(pm)
 +
imageWidget.setScaledContents(True)
 +
imageWidget.show()
 +
</pre>
 +
 
 +
[[Image:MatplotlibExample.png]]
 +
 
 +
====Plot in Slicer Jupyter notebook====
 +
 
 +
<pre>
 +
try:
 +
  import matplotlib
 +
except ModuleNotFoundError:
 +
  pip_install('matplotlib')
 +
  import matplotlib
 +
 
 +
matplotlib.use('Agg')
 +
from pylab import *
 +
 
 +
t1 = arange(0.0, 5.0, 0.1)
 +
t2 = arange(0.0, 5.0, 0.02)
 +
t3 = arange(0.0, 2.0, 0.01)
 +
 
 +
subplot(211)
 +
plot(t1, cos(2*pi*t1)*exp(-t1), 'bo', t2, cos(2*pi*t2)*exp(-t2), 'k')
 +
grid(True)
 +
title('A tale of 2 subplots')
 +
ylabel('Damped')
 +
 
 +
subplot(212)
 +
plot(t3, cos(2*pi*t3), 'r--')
 +
grid(True)
 +
xlabel('time (s)')
 +
ylabel('Undamped')
 +
savefig('MatplotlibExample.png')
 +
display(filename='MatplotlibExample.png', type="image/png", binary=True)
 +
</pre>
 +
 
 +
[[Image:JupyterNotebookMatplotlibExample.png]]
 +
 
 +
====Interactive plot using wxWidgets GUI toolkit====
 +
 
 +
<pre>
 +
try:
 +
  import matplotlib
 +
  import wx
 +
except ModuleNotFoundError:
 +
  pip_install('matplotlib wxPython')
 +
  import matplotlib
 +
 
 +
# Get a volume from SampleData and compute its histogram
 +
import SampleData
 +
import numpy as np
 +
volumeNode = SampleData.SampleDataLogic().downloadMRHead()
 +
histogram = np.histogram(arrayFromVolume(volumeNode), bins=50)
 +
 
 +
# Set matplotlib to use WXAgg backend
 +
import matplotlib
 +
matplotlib.use('WXAgg')
 +
 
 +
# Show an interactive plot
 +
import matplotlib.pyplot as plt
 +
fig, ax = plt.subplots()
 +
ax.plot(histogram[1][1:], histogram[0].astype(float))
 +
ax.grid(True)
 +
ax.set_ylim((0, 4e5))
 +
plt.show(block=False)
 +
</pre>
 +
 
 +
[[Image:InteractiveMatplotlibExample.png]]
 +
 
 +
==Execute external applications==
 +
 
 +
How to run external applications from Slicer.
 +
 
 +
===Run process in default environment===
 +
 
 +
When a process is launched from Slicer then by default Slicer's ITK, VTK, Qt, etc. libraries are used. If an external application has its own version of these libraries, then the application is expected to crash. To prevent crashing, the application must be run in the environment where Slicer started up (without all Slicer-specific library paths). This startup environment can be retrieved using ''slicer.util.startupEnvironment()''.
 +
 
 +
Example: run Python3 script from Slicer:
 +
 
 +
<pre>
 +
command_to_execute = ["/usr/bin/python3", "-c", "print('hola')"]
 +
from subprocess import check_output
 +
check_output(
 +
  command_to_execute,
 +
  env=slicer.util.startupEnvironment()
 +
  )
 +
</pre>
 +
 
 +
will output:
 +
<pre>
 +
'hola\n'
 +
</pre>
 +
 
 +
On some systems, ''shell=True'' must be specified as well.
 +
 
 +
==Manage extensions==
  
Example: run Python3 script from Slicer:
+
===Download and install extension===
  
 
<pre>
 
<pre>
command_to_execute = ["/usr/bin/python3", "-c", "print('hola')"]
+
extensionName = 'SlicerIGT'
from subprocess import check_output
+
em = slicer.app.extensionsManagerModel()
check_output(
+
if not em.isExtensionInstalled(extensionName):
  command_to_execute,
+
    extensionMetaData = em.retrieveExtensionMetadataByName(extensionName)
  env=slicer.util.startupEnvironment()
+
    url = em.serverUrl().toString()+'/download/item/'+extensionMetaData['item_id']
  )
+
    extensionPackageFilename = slicer.app.temporaryPath+'/'+extensionMetaData['md5']
 +
    slicer.util.downloadFile(url, extensionPackageFilename)
 +
    em.installExtension(extensionPackageFilename)
 +
    slicer.util.restart()
 
</pre>
 
</pre>
 
will output:
 
<pre>
 
'hola\n'
 
</pre>
 
 
On some systems, ''shell=True'' must be specified as well.
 

Revision as of 13:35, 13 February 2020

Home < Documentation < Nightly < ScriptRepository


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


Contents


Community-contributed modules

The examples in this section are Scripted Modules that provide a user interface in the module panel along with specialized implementation logic.

Usage: save the .py file to a directory, add the directory to the additional module paths in the Slicer application settings (choose in the menu: Edit / Application settings, click Modules, click >> next to Additional module paths, click Add, and choose the .py file's location).

More information about python scripted modules and more usage examples can be found in the Python scripting wiki page.

Filters

DICOM

Informatics

  • MarkupsInfo.py: Compute the total length between all the points of a markup list.
  • LineProfile.py: Compute intensity profile in a volume along a line.

Community-contributed examples

Usage: Copy-paste the shown code lines or linked .py file contents into Python console in Slicer. Or save them to a file and run them using execfile.

Capture

  • Capture the full Slicer screen and save it into a file
 img = qt.QPixmap.grabWidget(slicer.util.mainWindow()).toImage()
 img.save('c:/tmp/test.png')
  • Capture all the views save it into a file:
import ScreenCapture
cap = ScreenCapture.ScreenCaptureLogic()
cap.showViewControllers(False)
cap.captureImageFromView(None,'c:/tmp/test.png')
cap.showViewControllers(True)
  • Capture a single view:
viewNodeID = 'vtkMRMLViewNode1'
import ScreenCapture
cap = ScreenCapture.ScreenCaptureLogic()
view = cap.viewFromNode(slicer.mrmlScene.GetNodeByID(viewNodeID))
cap.captureImageFromView(view,'c:/tmp/test.png')

Common values for viewNodeID: vtkMRMLSliceNodeRed, vtkMRMLSliceNodeYellow, vtkMRMLSliceNodeGreen, vtkMRMLViewNode1, vtkMRMLViewNode2. The ScreenCapture module can also create video animations of rotating views, slice sweeps, etc.

  • Capture a slice view sweep into a series of PNG files - for example, Red slice view, 30 images, from position -125.0 to 75.0, into c:/tmp folder, with name image_00001.png, image_00002.png, ...
import ScreenCapture
ScreenCapture.ScreenCaptureLogic().captureSliceSweep(getNode('vtkMRMLSliceNodeRed'), -125.0, 75.0, 30, "c:/tmp", "image_%05d.png")
  • Capture 3D view into PNG file with transparent background
renderWindow = slicer.app.layoutManager().threeDWidget(0).threeDView().renderWindow()
renderWindow.SetAlphaBitPlanes(1)
wti = vtk.vtkWindowToImageFilter()
wti.SetInputBufferTypeToRGBA()
wti.SetInput(renderWindow)
writer = vtk.vtkPNGWriter()
writer.SetFileName("c:/tmp/screenshot.png")
writer.SetInputConnection(wti.GetOutputPort())
writer.Write()

Launching Slicer

  • How to open an .mrb file with Slicer at the command line?
 Slicer.exe --python-code "slicer.util.loadScene( 'f:/2013-08-23-Scene.mrb' )"
  • How to run a script in the Slicer environment in batch mode (without showing any graphical user interface)?
 Slicer.exe --python-code "doSomething; doSomethingElse; etc." --testing --no-splash --no-main-window

Load volume from file

When loading a volume from file, it is recommended to set returnNode=True to retrieve the loaded volume node.

loadedVolumeNode = slicer.util.loadVolume('c:/Users/abc/Documents/MRHead.nrrd')
  • Get a MRML node in the scene based on the node name and call methods of that object. For the MRHead sample data:
 vol=slicer.util.getNode('MR*')
 vol.GetImageData().GetDimensions()

Show volume rendering automatically when a volume is loaded

To show volume rendering of a volume automatically when it is loaded, add the lines below to your .slicerrc file.

@vtk.calldata_type(vtk.VTK_OBJECT)
def onNodeAdded(caller, event, calldata):
  node = calldata
  if isinstance(node, slicer.vtkMRMLVolumeNode):
    # Call showVolumeRendering using a timer instead of calling it directly
    # to allow the volume loading to fully complete.
    qt.QTimer.singleShot(0, lambda: showVolumeRendering(node))

def showVolumeRendering(volumeNode):
  print("Show volume rendering of node "+volumeNode.GetName())
  volRenLogic = slicer.modules.volumerendering.logic()
  displayNode = volRenLogic.CreateDefaultVolumeRenderingNodes(volumeNode)
  displayNode.SetVisibility(True)
  scalarRange = volumeNode.GetImageData().GetScalarRange()
  if scalarRange[1]-scalarRange[0] < 1500:
    # small dynamic range, probably MRI
    displayNode.GetVolumePropertyNode().Copy(volRenLogic.GetPresetByName('MR-Default'))
  else:
    # larger dynamic range, probably CT
    displayNode.GetVolumePropertyNode().Copy(volRenLogic.GetPresetByName('CT-Chest-Contrast-Enhanced'))
    
slicer.mrmlScene.AddObserver(slicer.vtkMRMLScene.NodeAddedEvent, onNodeAdded)

Automatically load volumes that are copied into a folder

This example shows how to implement a simple background task by using a timer. The background task is to check for any new volume files in folder and if there is any then automatically load it.

There are more efficient methods for file system monitoring or exchanging image data in real-time (for example, using OpenIGTLink), the example below is just for demonstration purposes.

incomingVolumeFolder = "c:/tmp/incoming"
incomingVolumesProcessed = []

def checkForNewVolumes():
  # Check if there is a new file in the 
  from os import listdir
  from os.path import isfile, join
  for f in listdir(incomingVolumeFolder):
    if f in incomingVolumesProcessed:
      # this is an incoming file, it was already there
      continue
    filePath = join(incomingVolumeFolder, f)
    if not isfile(filePath):
      # ignore directories
      continue
    logging.info("Loading new file: "+f)
    incomingVolumesProcessed.append(f)
    slicer.util.loadVolume(filePath)
  # Check again in 3000ms
  qt.QTimer.singleShot(3000, checkForNewVolumes)

# Start monitoring
checkForNewVolumes()

DICOM

How to load DICOM files into the scene from a folder

This code loads all DICOM objects into the scene from a file folder. All the registered plugins are evaluated and the one with the highest confidence will be used to load the data. Files are imported into a temporary DICOM database, so the current Slicer DICOM database is not impacted.

 dicomDataDir = "c:/my/folder/with/dicom-files"  # input folder with DICOM files
 loadedNodeIDs = []  # this list will contain the list of all loaded node IDs
 
 from DICOMLib import DICOMUtils
 with DICOMUtils.TemporaryDICOMDatabase() as db:
   DICOMUtils.importDicom(dicomDataDir, db)
   patientUIDs = db.patients()
   for patientUID in patientUIDs:
     loadedNodeIDs.extend(DICOMUtils.loadPatientByUID(patientUID))

How to access top level tags of DICOM images imported into Slicer? For example, to print the first patient's first study's first series' "0020,0032" field:

 db=slicer.dicomDatabase
 patientList=db.patients()
 studyList=db.studiesForPatient(patientList[0])
 seriesList=db.seriesForStudy(studyList[0])
 fileList=db.filesForSeries(seriesList[0])
 # Note, fileValue accesses the database of cached top level tags
 # (nested tags are not included)
 print(db.fileValue(fileList[0],'0020,0032'))

How to access DICOM tags nested in a sequence

 db=slicer.dicomDatabase
 patientList=db.patients()
 studyList=db.studiesForPatient(patientList[0])
 seriesList=db.seriesForStudy(studyList[0])
 fileList=db.filesForSeries(seriesList[0])
 # use pydicom to access the full header, which requires
 # re-reading the dataset instead of using the database cache
 import pydicom
 pydicom.dcmread(fileList[0])
 ds.CTExposureSequence[0].ExposureModulationType

How to access tag of a volume loaded from DICOM? For example, get the patient position stored in a volume:

 volumeName='2: ENT IMRT'
 n=slicer.util.getNode(volumeName)
 instUids=n.GetAttribute('DICOM.instanceUIDs').split()
 filename=slicer.dicomDatabase.fileForInstance(instUids[0])
 print(slicer.dicomDatabase.fileValue(filename,'0018,5100'))

How to access tag of an item in the Subject Hierachy tree? For example, get the content time tag of a structure set:

 rtStructName = '3: RTSTRUCT: PROS'
 rtStructNode = slicer.util.getNode(rtStructName)
 shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)
 rtStructShItemID = shNode.GetItemByDataNode(rtStructNode)
 ctSliceInstanceUids = shNode.GetItemAttribute(rtStructShItemID, 'DICOM.ReferencedInstanceUIDs').split()
 filename = slicer.dicomDatabase.fileForInstance(ctSliceInstanceUids[0])
 print(slicer.dicomDatabase.fileValue(filename,'0008,0033'))

How to get path and filename of a loaded DICOM volume?

 def pathFromNode(node):
   storageNode=node.GetStorageNode()
   if storageNode is not None: # loaded via drag-drop
       filepath=storageNode.GetFullNameFromFileName()
   else: # loaded via DICOM browser
       instanceUIDs=node.GetAttribute('DICOM.instanceUIDs').split()
       filepath=slicer.dicomDatabase.fileForInstance(instUids[0])
   return filepath
 
 # example:
 node=slicer.util.getNode('volume1')
 path=self.pathFromNode(node)
 print("DICOM path=%s" % path)

How can I convert DICOM to NRRD on the command line?

/Applications/Slicer-4.6.2.app/Contents/MacOS/Slicer --no-main-window --python-code "node=slicer.util.loadVolume('/tmp/series/im0.dcm'); slicer.util.saveNode(node, '/tmp/output.nrrd'); exit()"

The same can be done on windows by using the top level Slicer.exe. Be sure to use forward slashes in the pathnames within quotes on the command line.

Export a volume to DICOM file format

volumeNode = getNode('CTChest')
outputFolder = "c:/tmp/dicom-output"

# Create patient and study and put the volume under the study
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)
patientItemID = shNode.CreateSubjectItem(shNode.GetSceneItemID(), "test patient")
studyItemID = shNode.CreateStudyItem(patientItemID, "test study")
volumeShItemID = shNode.GetItemByDataNode(volumeNode)
shNode.SetItemParent(volumeShItemID, studyItemID)

import DICOMScalarVolumePlugin
exporter = DICOMScalarVolumePlugin.DICOMScalarVolumePluginClass()
exportables = exporter.examineForExport(volumeShItemID)
for exp in exportables:
  exp.directory = outputFolder

exporter.export(exportables)

Customize table columns in DICOM browser

# Get browser and database
dicomBrowser = slicer.modules.dicom.widgetRepresentation().self().dicomBrowser
dicomDatabase = dicomBrowser.database() # Need to go this way, do not use slicer.dicomDatabase for this

# Change column order
dicomDatabase.setWeightForField('Series', 'SeriesDescription', 7)
dicomDatabase.setWeightForField('Studies', 'StudyDescription', 6)
# Change column visibility
dicomDatabase.setVisibilityForField('Patients', 'PatientsBirthDate', False)
# Change column name
dicomDatabase.setDisplayedNameForField('Series', 'DisplayedCount', 'Number of images')
# Customize table manager in DICOM browser
dicomTableManager = dicomBrowser.dicomTableManager()
dicomTableManager.selectionMode = qt.QAbstractItemView.SingleSelection
dicomTableManager.autoSelectSeries = False

Toolbar functions

  • How to turn on slice intersections in the crosshair menu on the toolbar:
viewNodes = slicer.util.getNodesByClass('vtkMRMLSliceCompositeNode')
for viewNode in viewNodes:
  viewNode.SetSliceIntersectionVisibility(1)

How to find similar functions? For this one I searched for "slice intersections" text in the whole slicer source code, found that the function is implemented in Base\QTGUI\qSlicerViewersToolBar.cxx, then translated the qSlicerViewersToolBarPrivate::setSliceIntersectionVisible(bool visible) method to Python.

Manipulating objects in the slice viewer

  • How to define/edit a circular region of interest in a slice viewer?

Drop two markup points on a slice view and copy-paste the code below into the Python console. After this, as you move the markups you’ll see a circle following the markups.

# Update the sphere from the fiducial points
def UpdateSphere(param1, param2):  
  import math
  centerPointCoord = [0.0, 0.0, 0.0]
  markups.GetNthFiducialPosition(0,centerPointCoord)
  circumferencePointCoord = [0.0, 0.0, 0.0]
  markups.GetNthFiducialPosition(1,circumferencePointCoord)
  sphere.SetCenter(centerPointCoord)
  radius=math.sqrt((centerPointCoord[0]-circumferencePointCoord[0])**2+(centerPointCoord[1]-circumferencePointCoord[1])**2+(centerPointCoord[2]-circumferencePointCoord[2])**2)
  sphere.SetRadius(radius)
  sphere.SetPhiResolution(30)
  sphere.SetThetaResolution(30)
  sphere.Update()

# Get markup node from scene
markups=slicer.util.getNode('F')
sphere = vtk.vtkSphereSource()
UpdateSphere(0,0)
 
# Create model node and add to scene
modelsLogic = slicer.modules.models.logic()
model = modelsLogic.AddModel(sphere.GetOutput())
model.GetDisplayNode().SetSliceIntersectionVisibility(True)
model.GetDisplayNode().SetSliceIntersectionThickness(3)
model.GetDisplayNode().SetColor(1,1,0)
 
# Call UpdateSphere whenever the fiducials are changed
markups.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, UpdateSphere, 2)

Measure angle between two slice planes

Measure angle between red and yellow slice nodes. Whenever any of the slice nodes are moved, the updated angle is printed on the console.

sliceNodeIds = ['vtkMRMLSliceNodeRed', 'vtkMRMLSliceNodeYellow']

# Print angles between slice nodes
def ShowAngle(unused1=None, unused2=None):
    sliceNormalVector = []
    for sliceNodeId in sliceNodeIds:
        sliceToRAS = slicer.mrmlScene.GetNodeByID(sliceNodeId).GetSliceToRAS()
        sliceNormalVector.append([sliceToRAS.GetElement(0,2), sliceToRAS.GetElement(1,2), sliceToRAS.GetElement(2,2)])
    angleRad = vtk.vtkMath.AngleBetweenVectors(sliceNormalVector[0], sliceNormalVector[1])
    angleDeg = vtk.vtkMath.DegreesFromRadians(angleRad)
    print('Angle between slice planes = {0:0.3f}'.format(angleDeg))

# Observe slice node changes
for sliceNodeId in sliceNodeIds:
    slicer.mrmlScene.GetNodeByID(sliceNodeId).AddObserver(vtk.vtkCommand.ModifiedEvent, ShowAngle)

# Print current angle
ShowAngle()

Set slice position and orientation from 3 markup fiducials

Drop 3 markup points in the scene and copy-paste the code below into the Python console. After this, as you move the markups you’ll see the red slice view position and orientation will be set to make it fit to the 3 points.

# Update plane from fiducial points
def UpdateSlicePlane(param1=None, param2=None):
  # Get point positions as numpy array
  import numpy as np
  nOfFiduciallPoints = markups.GetNumberOfFiducials()
  if nOfFiduciallPoints < 3:
    return  # not enough points
  points = np.zeros([3,nOfFiduciallPoints])
  for i in range(0, nOfFiduciallPoints):
    markups.GetNthFiducialPosition(i, points[:,i])
  # Compute plane position and normal
  planePosition = points.mean(axis=1)
  planeNormal = np.cross(points[:,1] - points[:,0], points[:,2] - points[:,0])
  planeX = points[:,1] - points[:,0]
  sliceNode.SetSliceToRASByNTP(planeNormal[0], planeNormal[1], planeNormal[2],
    planeX[0], planeX[1], planeX[2],
    planePosition[0], planePosition[1], planePosition[2], 0)

# Get markup node from scene
sliceNode = slicer.app.layoutManager().sliceWidget('Red').mrmlSliceNode()
markups = slicer.util.getNode('F')

# Update slice plane manually
UpdateSlicePlane()

# Update slice plane automatically whenever points are changed
markupObservation = [markups, markups.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, UpdateSlicePlane, 2)]

To stop automatic updates, run this:

markupObservation[0].RemoveObserver(markupObservation[1])


Set slice position and orientation from a normal vector and position

This code snippet shows how to display a slice view defined by a normal vector and position in an anatomically sensible way: rotating slice view so that "up" direction (or "right" direction) is towards an anatomical axis.

def setSlicePoseFromSliceNormalAndPosition(sliceNode, sliceNormal, slicePosition, defaultViewUpDirection=None, backupViewRightDirection=None):
    """
    Set slice pose from the provided plane normal and position. View up direction is determined automatically,
    to make view up point towards defaultViewUpDirection.
    :param defaultViewUpDirection Slice view will be spinned in-plane to match point approximately this up direction. Default: patient superior.
    :param backupViewRightDirection Slice view will be spinned in-plane to match point approximately this right direction
        if defaultViewUpDirection is too similar to sliceNormal. Default: patient left.
    """
    # Fix up input directions
    if defaultViewUpDirection is None:
        defaultViewUpDirection = [0,0,1]
    if backupViewRightDirection is None:
        backupViewRightDirection = [-1,0,0]
    if sliceNormal[1]>=0:
        sliceNormalStandardized = sliceNormal
    else:
        sliceNormalStandardized = [-sliceNormal[0], -sliceNormal[1], -sliceNormal[2]]
    # Compute slice axes
    sliceNormalViewUpAngle = vtk.vtkMath.AngleBetweenVectors(sliceNormalStandardized, defaultViewUpDirection)
    angleTooSmallThresholdRad = 0.25 # about 15 degrees
    if sliceNormalViewUpAngle > angleTooSmallThresholdRad and sliceNormalViewUpAngle < vtk.vtkMath.Pi() - angleTooSmallThresholdRad:
        viewUpDirection = defaultViewUpDirection
        sliceAxisY = viewUpDirection
        sliceAxisX = [0, 0, 0]
        vtk.vtkMath.Cross(sliceAxisY, sliceNormalStandardized, sliceAxisX)
    else:
        sliceAxisX = backupViewRightDirection
    # Set slice axes
    sliceNode.SetSliceToRASByNTP(sliceNormalStandardized[0], sliceNormalStandardized[1], sliceNormalStandardized[2],
        sliceAxisX[0], sliceAxisX[1], sliceAxisX[2],
        slicePosition[0], slicePosition[1], slicePosition[2], 0)

# Example usage:
sliceNode = getNode('vtkMRMLSliceNodeRed')
transformNode = getNode('Transform_3')
transformMatrix = vtk.vtkMatrix4x4()
transformNode.GetMatrixTransformToParent(transformMatrix)
sliceNormal = [transformMatrix.GetElement(0,2), transformMatrix.GetElement(1,2), transformMatrix.GetElement(2,2)]
slicePosition = [transformMatrix.GetElement(0,3), transformMatrix.GetElement(1,3), transformMatrix.GetElement(2,3)]
setSlicePoseFromSliceNormalAndPosition(sliceNode, sliceNormal, slicePosition)

Switching to markup fiducial placement mode

To activate a fiducial placement mode, both interaction mode has to be set and a fiducial node has to be selected:

interactionNode = slicer.app.applicationLogic().GetInteractionNode()
selectionNode = slicer.app.applicationLogic().GetSelectionNode()
selectionNode.SetReferenceActivePlaceNodeClassName("vtkMRMLMarkupsFiducialNode")
fiducialNode = slicer.vtkMRMLMarkupsFiducialNode()
slicer.mrmlScene.AddNode(fiducialNode)
fiducialNode.CreateDefaultDisplayNodes() 
selectionNode.SetActivePlaceNodeID(fiducialNode.GetID())
interactionNode.SetCurrentInteractionMode(interactionNode.Place)

Alternatively, qSlicerMarkupsPlaceWidget widget can be used to initiate markup placement:

# Temporary markups node
markupsNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsFiducialNode")

def placementModeChanged(active):
  print("Placement: " +("active" if active else "inactive"))
  # You can inspect what is in the markups node here, delete the temporary markup node, etc.

# Create and set up widget that contains a single "place markup" button. The widget can be placed in the module GUI.
placeWidget = slicer.qSlicerMarkupsPlaceWidget()
placeWidget.setMRMLScene(slicer.mrmlScene)
placeWidget.setCurrentNode(markupsNode)
placeWidget.buttonsVisible=False
placeWidget.placeButton().show()
placeWidget.connect('activeMarkupsFiducialPlaceModeChanged(bool)', placementModeChanged)
placeWidget.show()

Change markup fiducial display properties

Display properties are stored in display node(s) associated with the fiducial node.

fiducialNode = getNode('F')
fiducialDisplayNode = fiducialNode.GetDisplayNode()
fiducialDisplayNode.SetVisibility(False) # Hide all points
fiducialDisplayNode.SetVisibility(True) # Show all points
fiducialDisplayNode.SetSelectedColor(1,1,0) # Set color to yellow
fiducialDisplayNode.SetViewNodeIDs(["vtkMRMLSliceNodeRed", "vtkMRMLViewNode1"]) # Only show in red slice view and first 3D view

Get a notification if a markup point position is modified

Event management of Slicer-4.11 version is still subject to change. The example below shows how point manipulation can be observed now.

def onMarkupChanged(caller,event):
    markupsNode = caller
    sliceView = markupsNode.GetAttribute('Markups.MovingInSliceView')
    movingMarkupIndex = markupsNode.GetDisplayNode().GetActiveControlPoint()
    if movingMarkupIndex >= 0:
        pos = [0,0,0]
        markupsNode.GetNthFiducialPosition(movingMarkupIndex, pos)
        isPreview = markupsNode.GetNthControlPointPositionStatus(movingMarkupIndex) == slicer.vtkMRMLMarkupsNode.PositionPreview
        if isPreview:
            logging.info("Point {0} is previewed at {1} in slice view {2}".format(movingMarkupIndex, pos, sliceView))
        else:
            logging.info("Point {0} was moved {1} in slice view {2}".format(movingMarkupIndex, pos, sliceView))
    else:
        logging.info("Points modified: slice view = {0}".format(sliceView))

def onMarkupStartInteraction(caller, event):
    markupsNode = caller
    sliceView = markupsNode.GetAttribute('Markups.MovingInSliceView')
    movingMarkupIndex = markupsNode.GetDisplayNode().GetActiveControlPoint()    
    logging.info("Start interaction: point ID = {0}, slice view = {1}".format(movingMarkupIndex, sliceView))

def onMarkupEndInteraction(caller, event):
    markupsNode = caller
    sliceView = markupsNode.GetAttribute('Markups.MovingInSliceView')
    movingMarkupIndex = markupsNode.GetDisplayNode().GetActiveControlPoint()
    logging.info("End interaction: point ID = {0}, slice view = {1}".format(movingMarkupIndex, sliceView))

markupsNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsFiducialNode")
markupsNode.CreateDefaultDisplayNodes()
markupsNode.AddFiducial(0,0,0)
markupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, onMarkupChanged)
markupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointStartInteractionEvent, onMarkupStartInteraction)
markupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointEndInteractionEvent, onMarkupEndInteraction)

Get a notification if a transform is modified

def onTransformNodeModified(transformNode, unusedArg2=None, unusedArg3=None):
  transformMatrix = vtk.vtkMatrix4x4()
  transformNode.GetMatrixTransformToWorld(transformMatrix)
  print("Position: [{0}, {1}, {2}]".format(transformMatrix.GetElement(0,3), transformMatrix.GetElement(1,3), transformMatrix.GetElement(2,3)))

transformNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLTransformNode")
transformNode.AddObserver(slicer.vtkMRMLTransformNode.TransformModifiedEvent, onTransformNodeModified)

Rotate a node around a specified point

Set up the scene:

  • Add a markup fiducial node (centerOfRotationMarkupsNode) with a single point to specify center of rotation.
  • Add a rotation transform (rotationTransformNode) that will be edited in Transforms module to specify rotation angles.
  • Add a transform (finalTransformNode) and apply it (not harden) to those nodes (images, models, etc.) that you want to rotate around the center of rotation point.

Then run the script below, go to Transforms module, select rotationTransformNode, and move rotation sliders.

# This markups fiducial node specifies the center of rotation
centerOfRotationMarkupsNode = getNode('F')
# This transform can be  edited in Transforms module
rotationTransformNode = getNode('LinearTransform_3')
# This transform has to be applied to the image, model, etc.
finalTransformNode = getNode('LinearTransform_4')

def updateFinalTransform(unusedArg1=None, unusedArg2=None, unusedArg3=None):
    rotationMatrix = vtk.vtkMatrix4x4()
    rotationTransformNode.GetMatrixTransformToParent(rotationMatrix)
    rotationCenterPointCoord = [0.0, 0.0, 0.0]
    centerOfRotationMarkupsNode.GetNthControlPointPositionWorld(0, rotationCenterPointCoord)
    finalTransform = vtk.vtkTransform()
    finalTransform.Translate(rotationCenterPointCoord)
    finalTransform.Concatenate(rotationMatrix)
    finalTransform.Translate(-rotationCenterPointCoord[0], -rotationCenterPointCoord[1], -rotationCenterPointCoord[2])
    finalTransformNode.SetAndObserveMatrixTransformToParent(finalTransform.GetMatrix())

# Manual initial update
updateFinalTransform()

# Automatic update when point is moved or transform is modified
rotationTransformNodeObserver = rotationTransformNode.AddObserver(slicer.vtkMRMLTransformNode.TransformModifiedEvent, updateFinalTransform)
centerOfRotationMarkupsNodeObserver = centerOfRotationMarkupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, updateFinalTransform)

# Execute these lines to stop automatic updates:
# rotationTransformNode.RemoveObserver(rotationTransformNodeObserver)
# centerOfRotationMarkupsNode.RemoveObserver(centerOfRotationMarkupsNodeObserver)

Rotate a node around a specified line

Set up the scene:

  • Add a markup line node (rotationAxisMarkupsNode) with 2 points to specify rotation axis.
  • Add a rotation transform (rotationTransformNode) that will be edited in Transforms module to specify rotation angle.
  • Add a transform (finalTransformNode) and apply it (not harden) to those nodes (images, models, etc.) that you want to rotate around the line.

Then run the script below, go to Transforms module, select rotationTransformNode, and move Edit / Rotation / IS slider.

# This markups fiducial node specifies the center of rotation
rotationAxisMarkupsNode = getNode('L')
# This transform can be edited in Transforms module (Edit / Rotation / IS slider)
rotationTransformNode = getNode('LinearTransform_3')
# This transform has to be applied to the image, model, etc.
finalTransformNode = getNode('LinearTransform_4')

def updateFinalTransform(unusedArg1=None, unusedArg2=None, unusedArg3=None):
    import numpy as np
    rotationAxisPoint1_World = np.zeros(3)
    rotationAxisMarkupsNode.GetNthControlPointPositionWorld(0, rotationAxisPoint1_World)
    rotationAxisPoint2_World = np.zeros(3)
    rotationAxisMarkupsNode.GetNthControlPointPositionWorld(1, rotationAxisPoint2_World)
    axisDirectionZ_World = rotationAxisPoint2_World-rotationAxisPoint1_World
    axisDirectionZ_World = axisDirectionZ_World/np.linalg.norm(axisDirectionZ_World)
    # Get transformation between world coordinate system and rotation axis aligne coordinate system
    worldToRotationAxisTransform = vtk.vtkMatrix4x4()
    p=vtk.vtkPlaneSource()
    p.SetNormal(axisDirectionZ_World)
    axisOrigin = np.array(p.GetOrigin())
    axisDirectionX_World = np.array(p.GetPoint1())-axisOrigin
    axisDirectionY_World = np.array(p.GetPoint2())-axisOrigin
    rotationAxisToWorldTransform = np.row_stack((np.column_stack((axisDirectionX_World, axisDirectionY_World, axisDirectionZ_World, rotationAxisPoint1_World)), (0, 0, 0, 1)))
    rotationAxisToWorldTransformMatrix = slicer.util.vtkMatrixFromArray(rotationAxisToWorldTransform)
    worldToRotationAxisTransformMatrix = slicer.util.vtkMatrixFromArray(np.linalg.inv(rotationAxisToWorldTransform))
    # Compute transformation chain
    rotationMatrix = vtk.vtkMatrix4x4()
    rotationTransformNode.GetMatrixTransformToParent(rotationMatrix)
    finalTransform = vtk.vtkTransform()
    finalTransform.Concatenate(rotationAxisToWorldTransformMatrix)
    finalTransform.Concatenate(rotationMatrix)
    finalTransform.Concatenate(worldToRotationAxisTransformMatrix)
    finalTransformNode.SetAndObserveMatrixTransformToParent(finalTransform.GetMatrix())

# Manual initial update
updateFinalTransform()

# Automatic update when point is moved or transform is modified
rotationTransformNodeObserver = rotationTransformNode.AddObserver(slicer.vtkMRMLTransformNode.TransformModifiedEvent, updateFinalTransform)
rotationAxisMarkupsNodeObserver = rotationAxisMarkupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, updateFinalTransform)

# Execute these lines to stop automatic updates:
# rotationTransformNode.RemoveObserver(rotationTransformNodeObserver)
# rotationAxisMarkupsNode.RemoveObserver(rotationAxisMarkupsNodeObserver)

Show a context menu when a markup point is clicked in a slice or 3D view


# Example actions to perform

def action1():
  print('Action1 on markup '+str(slicer.clickedMarkupIndex))

def action2():
  print('Action2 on markup '+str(slicer.clickedMarkupIndex))

def action3():
  print('Action3 on markup '+str(slicer.clickedMarkupIndex))

# Clicked markup index is saved here to let the action
# know which markup needs to be manipulated.
slicer.clickedMarkupIndex = -1
  
# Create a simple menu

menu = qt.QMenu()
a1 = qt.QAction("Test", slicer.util.mainWindow())
a1.connect('triggered()', action1)
menu.addAction(a1)
a2 = qt.QAction("Action", slicer.util.mainWindow())
a2.connect('triggered()', action1)
menu.addAction(a2)
a3 = qt.QAction("Here", slicer.util.mainWindow())
a3.connect('triggered()', action1)
menu.addAction(a3)

# Add observer to a markup fiducial list

@vtk.calldata_type(vtk.VTK_INT)
def markupClickedCallback(caller, eventId, callData):
  slicer.clickedMarkupIndex = callData
  print('Open menu on markup '+str(slicer.clickedMarkupIndex))
  menu.move(qt.QCursor.pos())
  menu.show()

markupsNode = getNode('F')
observerTag = markupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointClickedEvent, markupClickedCallback)

Write markup positions to JSON file

markupNode = getNode('F')
outputFileName = 'c:/tmp/test.json'

# Get markup positions
data = []
for fidIndex in range(markupNode.GetNumberOfFiducials()):
  coords=[0,0,0]
  markupNode.GetNthFiducialPosition(fidIndex,coords)
  data.append({'label': markupNode.GetNthFiducialLabel(), 'position': coords})

import json
with open(outputFileName, 'w') as outfile:
  json.dump(data, outfile)

Write annotation ROI to JSON file

roiNode = getNode('R')
outputFileName = "c:/tmp/test.json"

# Get annotation ROI data
center = [0,0,0]
radius = [0,0,0]
roiNode.GetControlPointWorldCoordinates(0, center)
roiNode.GetControlPointWorldCoordinates(1, radius)
data = {'center': radius, 'radius': radius}

# Write to json file
import json
with open(outputFileName, 'w') as outfile:
  json.dump(data, outfile)

Show a simple surface mesh as a model node

This example shows how to display a simple surface mesh (a box, created by a VTK source filter) as a model node.

# Create and set up polydata source
box = vtk.vtkCubeSource()
box.SetXLength(30)
box.SetYLength(20)
box.SetZLength(15)
box.SetCenter(10,20,5)

# Create a model node that displays output of the source
boxNode = slicer.modules.models.logic().AddModel(box.GetOutputPort())

# Adjust display properties
boxNode.GetDisplayNode().SetColor(1,0,0)
boxNode.GetDisplayNode().SetOpacity(0.8)

Measure distance of points from surface

This example computes closest distance of points (markups fiducial 'F') from a surface (model node 'mymodel') and writes results into a table.

markupsNode = getNode('F')
modelNode = getNode('mymodel')

# Transform model polydata to world coordinate system
if modelNode.GetParentTransformNode():
    transformModelToWorld = vtk.vtkGeneralTransform()
    slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(modelNode.GetParentTransformNode(), None, transformModelToWorld)
    polyTransformToWorld = vtk.vtkTransformPolyDataFilter()
    polyTransformToWorld.SetTransform(transformModelToWorld)
    polyTransformToWorld.SetInputData(modelNode.GetPolyData())
    polyTransformToWorld.Update()
    surface_World = polyTransformToWorld.GetOutput()
else:
    surface_World = modelNode.GetPolyData()

# Create arrays to store results
indexCol = vtk.vtkIntArray()
indexCol.SetName("Index")
labelCol = vtk.vtkStringArray()
labelCol.SetName("Name")
distanceCol = vtk.vtkDoubleArray()
distanceCol.SetName("Distance")

distanceFilter = vtk.vtkImplicitPolyDataDistance()
distanceFilter.SetInput(surface_World);
nOfFiduciallPoints = markupsNode.GetNumberOfFiducials()
for i in range(0, nOfFiduciallPoints):
    point_World = [0,0,0]
    markupsNode.GetNthControlPointPositionWorld(i, point_World)
    closestPointOnSurface_World = [0,0,0]
    closestPointDistance = distanceFilter.EvaluateFunctionAndGetClosestPoint(point_World, closestPointOnSurface_World)
    indexCol.InsertNextValue(i)
    labelCol.InsertNextValue(markupsNode.GetNthControlPointLabel(i))
    distanceCol.InsertNextValue(closestPointDistance)

# Create a table from result arrays
resultTableNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLTableNode", "Points from surface distance")
resultTableNode.AddColumn(indexCol)
resultTableNode.AddColumn(labelCol)
resultTableNode.AddColumn(distanceCol)

# Show table in view layout
slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutFourUpTableView)
slicer.app.applicationLogic().GetSelectionNode().SetReferenceActiveTableID(resultTableNode.GetID())
slicer.app.applicationLogic().PropagateTableSelection()

Add a texture mapped plane to the scene as a model

Note that model textures are not exposed in the GUI and are not saved in the scene

# Create model node
planeSource = vtk.vtkPlaneSource()
planeSource.SetOrigin(-50.0, -50.0, 0.0)
planeSource.SetPoint1(50.0, -50.0, 0.0)
planeSource.SetPoint2(-50.0, 50.0, 0.0)
model = slicer.modules.models.logic().AddModel(planeSource.GetOutputPort())

# Tune display properties
modelDisplay = model.GetDisplayNode()
modelDisplay.SetColor(1,1,0) # yellow
modelDisplay.SetBackfaceCulling(0)

# Add texture (just use image of an ellipsoid)
e = vtk.vtkImageEllipsoidSource()
modelDisplay.SetTextureImageDataConnection(e.GetOutputPort())

Get scalar values at surface of a model

The following script allows getting selected scalar value at a selected position of a model. Position can be selected by moving the mouse while holding down Shift key.

modelNode = getNode('sphere')
modelPointValues = modelNode.GetPolyData().GetPointData().GetArray("Normals")
markupsNode = slicer.mrmlScene.GetFirstNodeByName('F')

if not markupsNode:
  markupsNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsFiducialNode","F")

pointsLocator = vtk.vtkPointLocator() # could try using vtk.vtkStaticPointLocator() if need to optimize
pointsLocator.SetDataSet(modelNode.GetPolyData())
pointsLocator.BuildLocator()

def onMouseMoved(observer,eventid):  
  ras=[0,0,0]
  crosshairNode.GetCursorPositionRAS(ras)
  if markupsNode.GetNumberOfFiducials() == 0:
    markupsNode.AddFiducial(*ras)
  else:
    markupsNode.SetNthFiducialPosition(0,*ras)
  closestPointId = pointsLocator.FindClosestPoint(ras)
  closestPointValue = modelPointValues.GetTuple(closestPointId)
  print("RAS = " + repr(ras) + "    value = " + repr(closestPointValue))

crosshairNode=slicer.util.getNode('Crosshair') 
observationId = crosshairNode.AddObserver(slicer.vtkMRMLCrosshairNode.CursorPositionModifiedEvent, onMouseMoved)

# To stop printing of values run this:
# crosshairNode.RemoveObserver(observationId)

Select cells of a model using markups fiducial points

The following script selects cells of a model node that are closest to positions of markups fiducial points.

# Get input nodes
modelNode = slicer.util.getNode('Segment_1') # select cells in this model
markupsNode = slicer.util.getNode('F') # points will be selected at positions specified by this markups fiducial node

# Create scalar array that will store selection state
cellScalars = modelNode.GetMesh().GetCellData()
selectionArray = cellScalars.GetArray('selection')
if not selectionArray:
    selectionArray = vtk.vtkIntArray()
    selectionArray.SetName('selection')
    selectionArray.SetNumberOfValues(modelNode.GetMesh().GetNumberOfCells())
    selectionArray.Fill(0)
    cellScalars.AddArray(selectionArray)

# Set up coloring by selection array
modelNode.GetDisplayNode().SetActiveScalar("selection", vtk.vtkAssignAttribute.CELL_DATA)
modelNode.GetDisplayNode().SetAndObserveColorNodeID("vtkMRMLColorTableNodeWarm1")
modelNode.GetDisplayNode().SetScalarVisibility(True)

# Initialize cell locator
cell = vtk.vtkCellLocator()
cell.SetDataSet(modelNode.GetMesh())
cell.BuildLocator()

def onPointsModified(observer=None, eventid=None):
    global markupsNode, selectionArray
    selectionArray.Fill(0) # set all cells to non-selected by default
    markupPoints = slicer.util.arrayFromMarkupsControlPoints(markupsNode)
    closestPoint = [0.0, 0.0, 0.0]
    cellObj = vtk.vtkGenericCell()
    cellId = vtk.mutable(0)
    subId = vtk.mutable(0)
    dist2 = vtk.mutable(0.0)
    for markupPoint in markupPoints:
        cell.FindClosestPoint(markupPoint, closestPoint, cellObj, cellId, subId, dist2)
        closestCell = cellId.get()
        if closestCell >=0:
            selectionArray.SetValue(closestCell, 100) # set selected cell's scalar value to non-zero
    selectionArray.Modified()

# Initial update
onPointsModified()
# Automatic update each time when a markup point is modified
markupsNodeObserverTag = markupsNode.AddObserver(slicer.vtkMRMLMarkupsFiducialNode.PointModifiedEvent, onPointsModified)

# To stop updating selection, run this:
# markupsNode.RemoveObserver(markupsNodeObserverTag)

Export entire scene as VRML

Save all surface meshes displayed in the scene (models, markups, etc). Solid colors and coloring by scalar is preserved. Textures are not supported.

exporter = vtk.vtkVRMLExporter()
exporter.SetRenderWindow(slicer.app.layoutManager().threeDWidget(0).threeDView().renderWindow())
exporter.SetFileName('C:/tmp/something.wrl')
exporter.Write()

Export model to Blender, including color by scalar

modelNode = getNode("Model")
plyFilePath = "c:/tmp/model.ply"

modelDisplayNode = modelNode.GetDisplayNode()
triangles = vtk.vtkTriangleFilter()
triangles.SetInputConnection(modelDisplayNode.GetOutputPolyDataConnection())

plyWriter = vtk.vtkPLYWriter()
plyWriter.SetInputConnection(triangles.GetOutputPort())
lut = vtk.vtkLookupTable()
lut.DeepCopy(modelDisplayNode.GetColorNode().GetLookupTable())
lut.SetRange(modelDisplayNode.GetScalarRange())
plyWriter.SetLookupTable(lut)
plyWriter.SetArrayName(modelDisplayNode.GetActiveScalarName())

plyWriter.SetFileName(plyFilePath)
plyWriter.Write()

Export a tract (FiberBundle) to Blender, including color

Note: an interactive version of this script is now included in the SlicerDMRI extension (module code). After installing SlicerDMRI, go to Modules -> Diffusion -> Import and Export -> Export tractography to PLY (mesh).

The example below shows how to export a tractography "FiberBundleNode" to a PLY file:

lineDisplayNode = getNode("*LineDisplay*")
plyFilePath = "/tmp/fibers.ply"

tuber = vtk.vtkTubeFilter()
tuber.SetInputData(lineDisplayNode.GetOutputPolyData())
tuber.Update()
tubes = tuber.GetOutputDataObject(0)
scalars = tubes.GetPointData().GetArray(0)
scalars.SetName("scalars")

triangles = vtk.vtkTriangleFilter()
triangles.SetInputData(tubes)
triangles.Update()

colorNode = lineDisplayNode.GetColorNode()
lookupTable = vtk.vtkLookupTable()
lookupTable.DeepCopy(colorNode.GetLookupTable())
lookupTable.SetTableRange(0,1)

plyWriter = vtk.vtkPLYWriter()
plyWriter.SetInputData(triangles.GetOutput())
plyWriter.SetLookupTable(lookupTable)
plyWriter.SetArrayName("scalars")

plyWriter.SetFileName(plyFilePath)
plyWriter.Write()

Iterate over tract (FiberBundle) streamline points

This example shows how to access the points in each line of a FiberBundle as a numpy array (view).

from vtk.util.numpy_support import vtk_to_numpy

fb = getNode("FiberBundle_F") # <- fill in node ID here

# get point data as 1d array
points = slicer.util.arrayFromModelPoints(fb)

# get line cell ids as 1d array
line_ids = vtk_to_numpy(fb.GetPolyData().GetLines().GetData())

# VTK cell ids are stored as
#   [ N0 c0_id0 ... c0_id0
#     N1 c1_id0 ... c1_idN1 ]
# so we need to
# - read point count for each line (cell)
# - grab the ids in that range from `line_ids` array defined above
# - index the `points` array by those ids
cur_idx = 1
for _ in range(pd.GetLines().GetNumberOfCells()):
    # - read point count for this line (cell)
    count = lines[cur_idx - 1]

    # - grab the ids in that range from `lines`
    index_array = line_ids[ cur_idx : cur_idx + count]
    # update to the next range 
    cur_idx += count + 1

    # - index the point array by those ids
    line_points = points[index_array]

    # do work here

Clone a node

This example shows how to make a copy of any node that appears in Subject Hierarchy (in Data module).

# Get a node from SampleData that we will clone
import SampleData
nodeToClone = SampleData.SampleDataLogic().downloadMRHead()

# Clone the node
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)
itemIDToClone = shNode.GetItemByDataNode(nodeToClone)
clonedItemID = slicer.modules.subjecthierarchy.logic().CloneSubjectHierarchyItem(shNode, itemIDToClone)
clonedNode = shNode.GetItemDataNode(clonedItemID)

Clone a volume

This example shows how to clone the MRHead sample volume, including its pixel data and display settings.

sourceVolumeNode = slicer.util.getNode('MRHead')
volumesLogic = slicer.modules.volumes.logic()
clonedVolumeNode = volumesLogic.CloneVolume(slicer.mrmlScene, sourceVolumeNode, 'Cloned volume')

Create a new volume

This example shows how to create a new empty volume.

nodeName = "MyNewVolume"
imageSize = [512, 512, 512]
voxelType=vtk.VTK_UNSIGNED_CHAR
imageOrigin = [0.0, 0.0, 0.0]
imageSpacing = [1.0, 1.0, 1.0]
imageDirections = [[1,0,0], [0,1,0], [0,0,1]]
fillVoxelValue = 0

# Create an empty image volume, filled with fillVoxelValue
imageData = vtk.vtkImageData()
imageData.SetDimensions(imageSize)
imageData.AllocateScalars(voxelType, 1)
imageData.GetPointData().GetScalars().Fill(fillVoxelValue)
# Create volume node
volumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode", nodeName)
volumeNode.SetOrigin(imageOrigin)
volumeNode.SetSpacing(imageSpacing)
volumeNode.SetIJKToRASDirections(imageDirections)
volumeNode.SetAndObserveImageData(imageData)
volumeNode.CreateDefaultDisplayNodes()
volumeNode.CreateDefaultStorageNode()

Get value of a volume at specific voxel coordinates

This example shows how to get voxel value of "volumeNode" at "ijk" volume voxel coordinates.

volumeNode = slicer.util.getNode('MRHead')
ijk = [20,40,30]  # volume voxel coordinates

voxels = slicer.util.arrayFromVolume(volumeNode)  # get voxels as a numpy array
voxelValue = voxels[ijk[2], ijk[1], ijk[0]]  # note that numpy array index order is kji (not ijk)

Modify voxels in a volume

Typically the fastest and simplest way of modifying voxels is by using numpy operators. Voxels can be retrieved in a numpy array using the `array` method and modified using standard numpy methods. For example, threshold a volume:

nodeName = 'MRHead'
thresholdValue = 100
voxelArray = array(nodeName) # get voxels as numpy array
voxelArray[voxelArray < thresholdValue] = 0 # modify voxel values
getNode(nodeName).Modified() # at the end of all processing, notify Slicer that the image modification is completed

This example shows how to change voxels values of the MRHead sample volume. The values will be computed by function f(r,a,s,) = (r-10)*(r-10)+(a+15)*(a+15)+s*s.

volumeNode=slicer.util.getNode('MRHead')
ijkToRas = vtk.vtkMatrix4x4()
volumeNode.GetIJKToRASMatrix(ijkToRas)
imageData=volumeNode.GetImageData()
extent = imageData.GetExtent()
for k in range(extent[4], extent[5]+1):
  for j in range(extent[2], extent[3]+1):
    for i in range(extent[0], extent[1]+1):
      position_Ijk=[i, j, k, 1]
      position_Ras=ijkToRas.MultiplyPoint(position_Ijk)
      r=position_Ras[0]
      a=position_Ras[1]
      s=position_Ras[2]      
      functionValue=(r-10)*(r-10)+(a+15)*(a+15)+s*s
      imageData.SetScalarComponentFromDouble(i,j,k,0,functionValue)
imageData.Modified()

Get volume voxel coordinates from markup fiducial RAS coordinates

This example shows how to get voxel coordinate of a volume corresponding to a markup fiducial point position.

# Inputs
volumeNode = getNode('MRHead')
markupsNode = getNode('F')
markupsIndex = 0

# Get point coordinate in RAS
point_Ras = [0, 0, 0, 1]
markupsNode.GetNthFiducialWorldCoordinates(markupsIndex, point_Ras)

# If volume node is transformed, apply that transform to get volume's RAS coordinates
transformRasToVolumeRas = vtk.vtkGeneralTransform()
slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(None, volumeNode.GetParentTransformNode(), transformRasToVolumeRas)
point_VolumeRas = transformRasToVolumeRas.TransformPoint(point_Ras[0:3])

# Get voxel coordinates from physical coordinates
volumeRasToIjk = vtk.vtkMatrix4x4()
volumeNode.GetRASToIJKMatrix(volumeRasToIjk)
point_Ijk = [0, 0, 0, 1]
volumeRasToIjk.MultiplyPoint(np.append(point_VolumeRas,1.0), point_Ijk)
point_Ijk = [ int(round(c)) for c in point_Ijk[0:3] ]

# Print output
print(point_Ijk)

Get markup fiducial RAS coordinates from volume voxel coordinates

This example shows how to get position of maximum intensity voxel of a volume (determined by numpy, in IJK coordinates) in RAS coordinates so that it can be marked with a markup fiducial.

# Inputs
volumeNode = getNode('MRHead')
markupsNode = getNode('F')

# Get voxel position in IJK coordinate system
import numpy as np
volumeArray = slicer.util.arrayFromVolume(volumeNode)
# Get position of highest voxel value
point_Kji = np.where(volumeArray == volumeArray.max())
point_Ijk = [point_Kji[2][0], point_Kji[1][0], point_Kji[0][0]]

# Get physical coordinates from voxel coordinates
volumeIjkToRas = vtk.vtkMatrix4x4()
volumeNode.GetIJKToRASMatrix(volumeIjkToRas)
point_VolumeRas = [0, 0, 0, 1]
volumeIjkToRas.MultiplyPoint(np.append(point_Ijk,1.0), point_VolumeRas)

# If volume node is transformed, apply that transform to get volume's RAS coordinates
transformVolumeRasToRas = vtk.vtkGeneralTransform()
slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(volumeNode.GetParentTransformNode(), None, transformVolumeRasToRas)
point_Ras = transformVolumeRasToRas.TransformPoint(point_VolumeRas[0:3])

# Add a markup at the computed position and print its coordinates
markupsNode.AddFiducial(point_Ras[0], point_Ras[1], point_Ras[2], "max")
print(point_Ras)

Get the values of all voxels for a label value

If you have a background image called ‘Volume’ and a mask called ‘Volume-label’ created with the Editor you could do something like this:


import numpy
volume = array(‘Volume’)
label = array(‘Volume-label’)
points  = numpy.where( label == 1 )  # or use another label number depending on what you segmented
values  = volume[points] # this will be a list of the label values
values.mean() # should match the mean value of LabelStatistics calculation as a double-check
numpy.savetxt(‘values.txt’, values)

Access values in a DTI tensor volume

This example shows how to access individual tensors at the voxel level.

First load your DWI volume and estimate tensors to produce a DTI volume called ‘Output DTI Volume’

Then open the python window: View->Python interactor

Use this command to access tensors through numpy:

tensors = array('Output DTI Volume')

Type the following code into the Python window to access all tensor components using vtk commands:

volumeNode=slicer.util.getNode('Output DTI Volume')
imageData=volumeNode.GetImageData()
tensors = imageData.GetPointData().GetTensors()
extent = imageData.GetExtent()
idx = 0
for k in range(extent[4], extent[5]+1):
  for j in range(extent[2], extent[3]+1):
    for i in range(extent[0], extent[1]+1):
      tensors.GetTuple9(idx)
      idx += 1

Change window/level (brightness/contrast) or colormap of a volume

This example shows how to change window/level of the MRHead sample volume.

volumeNode = getNode('MRHead')
displayNode = volumeNode.GetDisplayNode()
displayNode.AutoWindowLevelOff()
displayNode.SetWindow(50)
displayNode.SetLevel(100)

Change color mapping from grayscale to rainbow:

displayNode.SetAndObserveColorNodeID('vtkMRMLColorTableNodeRainbow')

Make mouse left-click and drag on the image adjust window/level

In older Slicer versions, by default, left-click and drag in a slice view adjusted window/level of the displayed image. Window/level adjustment is now a new mouse mode that can be activated by clicking on its toolbar button or running this code:

slicer.app.applicationLogic().GetInteractionNode().SetCurrentInteractionMode(slicer.vtkMRMLInteractionNode.AdjustWindowLevel)

Create custom color table

This example shows how to create a new color table, for example with inverted color range from the default Ocean color table.

invertedocean = slicer.vtkMRMLColorTableNode()
invertedocean.SetTypeToUser()
invertedocean.SetNumberOfColors(256)
invertedocean.SetName("InvertedOcean")

for i in range(0,255):
    invertedocean.SetColor(i, 0.0, 1 - (i+1e-16)/255.0, 1.0, 1.0)

slicer.mrmlScene.AddNode(invertedocean)

Manipulate a Slice View

Change slice offset

Equivalent to moving the slider in slice view controller.

layoutManager = slicer.app.layoutManager()
red = layoutManager.sliceWidget('Red')
redLogic = red.sliceLogic()
# Print current slice offset position
print(redLogic.GetSliceOffset())
# Change slice position
redLogic.SetSliceOffset(20)

Change slice orientation

Get 'Red' slice node and rotate around X and Y axes.

sliceNode = slicer.app.layoutManager().sliceWidget('Red').mrmlSliceNode()
sliceToRas = sliceNode.GetSliceToRAS()
transform=vtk.vtkTransform()
transform.SetMatrix(SliceToRAS)
transform.RotateX(20)
transform.RotateY(15)
sliceToRas.DeepCopy(transform.GetMatrix())
sliceNode.UpdateMatrices()

Show slice views in 3D window

Equivalent to clicking 'eye' icon in the slice view controller.

layoutManager = slicer.app.layoutManager()
for sliceViewName in layoutManager.sliceViewNames():
  controller = layoutManager.sliceWidget(sliceViewName).sliceController()
  controller.setSliceVisible(True)

Reset field of view to show background volume maximized

Equivalent to click small rectangle button ("Adjust the slice viewer's field of view...") in the slice view controller.

slicer.util.resetSliceViews()

Rotate slice views to volume plane

Aligns slice views to volume axes, shows original image acquisition planes in slice views.

volumeNode = slicer.util.getNode('MRHead')
layoutManager = slicer.app.layoutManager()
for sliceViewName in layoutManager.sliceViewNames():
  layoutManager.sliceWidget(sliceViewName).mrmlSliceNode().RotateToVolumePlane(volumeNode)

Iterate over current visible slice views, and set foreground and background images

slicer.util.setSliceViewerLayers(background=mrVolume, foreground=ctVolume)

Internally, this method performs something like this:

layoutManager = slicer.app.layoutManager()
for sliceViewName in layoutManager.sliceViewNames():
     compositeNode = layoutManager.sliceWidget(sliceViewName).sliceLogic().GetSliceCompositeNode()
     # setup background volume
     compositeNode.SetBackgroundVolumeID(mrVolume.GetID())
     # setup foreground volume
     compositeNode.SetForegroundVolumeID(ctVolume.GetID())
     # change opacity
     compositeNode.SetForegroundOpacity(0.3)

Show a volume in slice views

Recommended:

volumeNode = slicer.util.getNode('YourVolumeNode')
slicer.util.setSliceViewerLayers(background=volumeNode)

or

Show volume in all visible views where volume selection propagation is enabled:

volumeNode = slicer.util.getNode('YourVolumeNode')
applicationLogic = slicer.app.applicationLogic()
selectionNode = applicationLogic.GetSelectionNode()
selectionNode.SetSecondaryVolumeID(volumeNode.GetID())
applicationLogic.PropagateForegroundVolumeSelection(0) 

or

Show volume in selected views:

n =  slicer.util.getNode('YourVolumeNode')
for color in ['Red', 'Yellow', 'Green']:
    slicer.app.layoutManager().sliceWidget(color).sliceLogic().GetSliceCompositeNode().SetForegroundVolumeID(n.GetID())

Change opacity of foreground volume in slice views

slicer.util.setSliceViewerLayers(foregroundOpacity=0.4)

or

Change opacity in a selected view

lm = slicer.app.layoutManager()
sliceLogic = lm.sliceWidget('Red').sliceLogic()
compositeNode = sliceLogic.GetSliceCompositeNode()
compositeNode.SetForegroundOpacity(0.4)

Fit slice plane to markup fiducials

sliceNode = slicer.mrmlScene.GetNodeByID("vtkMRMLSliceNodeRed")
markupsNode = slicer.mrmlScene.GetFirstNodeByName("F")
# Get markup point positions as numpy arrays
import numpy as np
p1 = np.zeros(3)
p2 = np.zeros(3)
p3 = np.zeros(3)
markupsNode.GetNthFiducialPosition(0, p1)
markupsNode.GetNthFiducialPosition(1, p2)
markupsNode.GetNthFiducialPosition(2, p3)
# Get plane axis directions
n = np.cross(p2-p1, p2-p3) # plane normal direction
n = n/np.linalg.norm(n)
t = np.cross([0.0, 0.0, 1], n) # plane transverse direction
t = t/np.linalg.norm(t)
# Set slice plane orientation and position
sliceNode.SetSliceToRASByNTP(n[0], n[1], n[2], t[0], t[1], t[2], p1[0], p1[1], p1[2], 0)

Save a series of images from a Slice View

You can use ScreenCapture module to capture series of images. To do it programmatically, save the following into a file such as '/tmp/record.py' and then in the slicer python console type "execfile('/tmp/record.py')"

layoutName = 'Green'
imagePathPattern = '/tmp/image-%03d.png'
steps = 10

widget = slicer.app.layoutManager().sliceWidget(layoutName)
view = widget.sliceView()
logic = widget.sliceLogic()
bounds = [0,]*6
logic.GetSliceBounds(bounds)

for step in range(steps):
    offset = bounds[4] + step/(1.*steps) * (bounds[5]-bounds[4])
    logic.SetSliceOffset(offset)
    view.forceRender()
    image = qt.QPixmap.grabWidget(view).toImage()
    image.save(imagePathPattern % step)

Rasterize a model and save it to a series of image files

This example shows how to generate a stack of image files from an STL file:

inputModelFile = "/some/input/folder/SomeShape.stl"
outputDir = "/some/output/folder"
outputVolumeLabelValue = 100
outputVolumeSpacingMm = [0.5, 0.5, 0.5]
outputVolumeMarginMm = [10.0, 10.0, 10.0]

# Read model
inputModel = slicer.util.loadModel(inputModelFile)

# Determine output volume geometry and create a corresponding reference volume
import math
import numpy as np
bounds = np.zeros(6)
inputModel.GetBounds(bounds)
imageData = vtk.vtkImageData()
imageSize = [ int((bounds[axis*2+1]-bounds[axis*2]+outputVolumeMarginMm[axis]*2.0)/outputVolumeSpacingMm[axis]) for axis in range(3) ]
imageOrigin = [ bounds[axis*2]-outputVolumeMarginMm[axis] for axis in range(3) ]
imageData.SetDimensions(imageSize)
imageData.AllocateScalars(vtk.VTK_UNSIGNED_CHAR, 1)
imageData.GetPointData().GetScalars().Fill(0)
referenceVolumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode")
referenceVolumeNode.SetOrigin(imageOrigin)
referenceVolumeNode.SetSpacing(outputVolumeSpacingMm)
referenceVolumeNode.SetAndObserveImageData(imageData)
referenceVolumeNode.CreateDefaultDisplayNodes()

# Convert model to labelmap
seg = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSegmentationNode')
seg.SetReferenceImageGeometryParameterFromVolumeNode(referenceVolumeNode)
slicer.modules.segmentations.logic().ImportModelToSegmentationNode(inputModel, seg)
seg.CreateBinaryLabelmapRepresentation()
outputLabelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')
slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(seg, outputLabelmapVolumeNode, referenceVolumeNode)
outputLabelmapVolumeArray = (slicer.util.arrayFromVolume(outputLabelmapVolumeNode) * outputVolumeLabelValue).astype('int8')

# Write labelmap volume to series of TIFF files
pip_install("imageio")
import imageio
for i in range(len(outputLabelmapVolumeArray)):
    imageio.imwrite(f'{outputDir}/image_{i:03}.tiff', outputLabelmapVolumeArray[i])

Save the scene into a new directory

# Create a new directory where the scene will be saved into
import time
sceneSaveDirectory = slicer.app.temporaryPath + "/saved-scene-" + time.strftime("%Y%m%d-%H%M%S")
if not os.access(sceneSaveDirectory, os.F_OK):
  os.makedirs(sceneSaveDirectory)

# Save the scene
if slicer.app.applicationLogic().SaveSceneToSlicerDataBundleDirectory(sceneSaveDirectory, None):
  logging.info("Scene saved to: {0}".format(sceneSaveDirectory))
else:
  logging.error("Scene saving failed") 

Save the scene into a single MRB file

# Generate file name
import time
sceneSaveFilename = slicer.app.temporaryPath + "/saved-scene-" + time.strftime("%Y%m%d-%H%M%S") + ".mrb"

# Save scene
if slicer.util.saveScene(sceneSaveFilename):
  logging.info("Scene saved to: {0}".format(sceneSaveFilename))
else:
  logging.error("Scene saving failed") 

Save a node to file

Save a transform node to file (should work with any other node type, if file extension is set to a supported one):

myNode = getNode("LinearTransform_3")

myStorageNode = myNode.CreateDefaultStorageNode()
myStorageNode.SetFileName("c:/tmp/something.tfm")
myStorageNode.WriteData(myNode)

Center the 3D View on the Scene

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

Rotate the 3D View

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

Display text in a 3D view or slice view

The easiest way to show information overlaid on a viewer is to use corner annotations.

view=slicer.app.layoutManager().threeDWidget(0).threeDView()
# Set text to "Something"
view.cornerAnnotation().SetText(vtk.vtkCornerAnnotation.UpperRight,"Something")
# Set color to red
view.cornerAnnotation().GetTextProperty().SetColor(1,0,0)
# Update the view
view.forceRender()

Hide slice view annotations (DataProbe)

# Disable slice annotations immediately
slicer.modules.DataProbeInstance.infoWidget.sliceAnnotations.sliceViewAnnotationsEnabled=False
slicer.modules.DataProbeInstance.infoWidget.sliceAnnotations.updateSliceViewFromGUI()
# Disable slice annotations persistently (after Slicer restarts)
settings = qt.QSettings()
settings.setValue('DataProbe/sliceViewAnnotations.enabled', 0)

Turning off interpolation

You can turn off interpolation for newly loaded volumes with this script from Steve Pieper.

def NoInterpolate(caller,event):
  for node in slicer.util.getNodes('*').values():
    if node.IsA('vtkMRMLScalarVolumeDisplayNode'):
      node.SetInterpolate(0)
	
slicer.mrmlScene.AddObserver(slicer.mrmlScene.NodeAddedEvent, NoInterpolate)

The below link explains how to put this in your startup script.

http://www.na-mic.org/Wiki/index.php/AHM2012-Slicer-Python#Refining_the_code_and_UI_with_slicerrc


Customize viewer layout

Show a custom layout of a 3D view on top of the red slice view:

customLayout = """
<layout type="vertical" split="true">
  <item>
   <view class="vtkMRMLViewNode" singletontag="1">
     <property name="viewlabel" action="default">1</property>
   </view>
  </item>
  <item>
   <view class="vtkMRMLSliceNode" singletontag="Red">
    <property name="orientation" action="default">Axial</property>
    <property name="viewlabel" action="default">R</property>
    <property name="viewcolor" action="default">#F34A33</property>
   </view>
  </item>
</layout>
"""

# Built-in layout IDs are all below 100, so you can choose any large random number
# for your custom layout ID.
customLayoutId=501

layoutManager = slicer.app.layoutManager()
layoutManager.layoutLogic().GetLayoutNode().AddLayoutDescription(customLayoutId, customLayout)                                         

# Switch to the new custom layout 
layoutManager.setLayout(customLayoutId)

See description of standard layouts (that can be used as examples) here: https://github.com/Slicer/Slicer/blob/master/Libs/MRML/Logic/vtkMRMLLayoutLogic.cxx

You can use this code snippet to add a button to the layout selector toolbar:

# Add button to layout selector toolbar for this custom layout
viewToolBar = mainWindow().findChild('QToolBar', 'ViewToolBar')
layoutMenu = viewToolBar.widgetForAction(viewToolBar.actions()[0]).menu()
layoutSwitchActionParent = layoutMenu  # use `layoutMenu` to add inside layout list, use `viewToolBar` to add next the standard layout list
layoutSwitchAction = layoutSwitchActionParent.addAction("My view") # add inside layout list
layoutSwitchAction.setData(layoutId)
layoutSwitchAction.setIcon(qt.QIcon(':Icons/Go.png'))
layoutSwitchAction.setToolTip('3D and slice view')
layoutSwitchAction.connect('triggered()', lambda layoutId = customLayoutId: slicer.app.layoutManager().setLayout(layoutId))

Customize keyboard shortcuts

Keyboard shortcuts can be specified for activating any Slicer feature by adding a couple of lines to your .slicerrc file.

For example, this script registers Ctrl+b, Ctrl+n, Ctrl+m, Ctrl+, keyboard shortcuts to switch between red, yellow, green, and 4-up view layouts.

shortcuts = [
    ('Ctrl+b', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpRedSliceView)),
    ('Ctrl+n', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpYellowSliceView)),
    ('Ctrl+m', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpGreenSliceView)),
    ('Ctrl+,', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutFourUpView))
    ]

for (shortcutKey, callback) in shortcuts:
    shortcut = qt.QShortcut(slicer.util.mainWindow())
    shortcut.setKey(qt.QKeySequence(shortcutKey))
    shortcut.connect( 'activated()', callback)

Here's an example for cycling through Segment Editor effects (requested on the forum for the SlicerMorph project).

def cycleEffect(delta=1):
    try:
        orderedNames = list(slicer.modules.SegmentEditorWidget.editor.effectNameOrder())
        allNames = slicer.modules.SegmentEditorWidget.editor.availableEffectNames()
        for name in allNames:
            try:
                orderedNames.index(name)
            except ValueError:
                orderedNames.append(name)
        orderedNames.insert(0, None)
        activeEffect = slicer.modules.SegmentEditorWidget.editor.activeEffect()
        if activeEffect:
            activeName = slicer.modules.SegmentEditorWidget.editor.activeEffect().name
        else:
            activeName = None
        newIndex = (orderedNames.index(activeName) + delta) % len(orderedNames)
        slicer.modules.SegmentEditorWidget.editor.setActiveEffectByName(orderedNames[newIndex])
    except AttributeError:
        # module not active
        pass

shortcuts = [
    ('`', lambda: cycleEffect(-1)),
    ('~', lambda: cycleEffect(1)),
    ]

for (shortcutKey, callback) in shortcuts:
    shortcut = qt.QShortcut(slicer.util.mainWindow())
    shortcut.setKey(qt.QKeySequence(shortcutKey))
    shortcut.connect( 'activated()', callback)

Disable certain user interactions in slice views

For example, disable slice browsing using mouse wheel and keyboard shortcuts in the red slice viewer:

interactorStyle = slicer.app.layoutManager().sliceWidget('Red').sliceView().sliceViewInteractorStyle()
interactorStyle.SetActionEnabled(interactorStyle.BrowseSlice, False)

Hide all slice view controllers:

lm = slicer.app.layoutManager()
for sliceViewName in lm.sliceViewNames():
  lm.sliceWidget(sliceViewName).sliceController().setVisible(False)

Hide all 3D view controllers:

lm = slicer.app.layoutManager()
for viewIndex in range(slicer.app.layoutManager().threeDViewCount):
  lm.threeDWidget(0).threeDController().setVisible(False)

Change default slice view orientation

You can left-right "flip" slice view orientation presets (show patient left side on left/right side of the screen) by copy-pasting the script below to your .slicerrc.py file.

# Axial slice axes:
#  1 0 0
#  0 1 0
#  0 0 1
axialSliceToRas=vtk.vtkMatrix3x3()

# Coronal slice axes:
#  1 0 0 
#  0 0 -1
#  0 1 0
coronalSliceToRas=vtk.vtkMatrix3x3()
coronalSliceToRas.SetElement(1,1, 0)
coronalSliceToRas.SetElement(1,2, -1)
coronalSliceToRas.SetElement(2,1, 1)
coronalSliceToRas.SetElement(2,2, 0)

# Replace orientation presets in all existing slice nodes and in the default slice node
sliceNodes = slicer.util.getNodesByClass('vtkMRMLSliceNode')
sliceNodes.append(slicer.mrmlScene.GetDefaultNodeByClass('vtkMRMLSliceNode'))
for sliceNode in sliceNodes:
  orientationPresetName = sliceNode.GetOrientation()
  sliceNode.RemoveSliceOrientationPreset("Axial")
  sliceNode.AddSliceOrientationPreset("Axial", axialSliceToRas)
  sliceNode.RemoveSliceOrientationPreset("Coronal")
  sliceNode.AddSliceOrientationPreset("Coronal", coronalSliceToRas)
  sliceNode.SetOrientation(orientationPresetName)


Set all slice views linked by default

You can make slice views linked by default (when application starts or the scene is cleared) by copy-pasting the script below to your .slicerrc.py file.

# Set linked slice views  in all existing slice composite nodes and in the default node
sliceCompositeNodes = slicer.util.getNodesByClass('vtkMRMLSliceCompositeNode')
defaultSliceCompositeNode = slicer.mrmlScene.GetDefaultNodeByClass('vtkMRMLSliceCompositeNode')
if not defaultSliceCompositeNode:
  defaultSliceCompositeNode = slicer.mrmlScene.CreateNodeByClass('vtkMRMLSliceCompositeNode')
  slicer.mrmlScene.AddDefaultNode(defaultSliceCompositeNode)
sliceCompositeNodes.append(defaultSliceCompositeNode)
for sliceCompositeNode in sliceCompositeNodes:
  sliceCompositeNode.SetLinkedControl(True)

Set crosshair jump mode to centered by default

You can change default slice jump mode (when application starts or the scene is cleared) by copy-pasting the script below to your .slicerrc.py file.

crosshair=slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLCrosshairNode")
crosshair.SetCrosshairBehavior(crosshair.CenteredJumpSlice)

Set up custom units in slice view ruler

For microscopy or micro-CT images you may want to switch unit to micrometer instead of the default mm. To do that, 1. change the unit in Application settings / Units and 2. update ruler display settings using the script below (it can be copied to your Application startup script):

lm = slicer.app.layoutManager()
for sliceViewName in lm.sliceViewNames():
  sliceView = lm.sliceWidget(sliceViewName).sliceView()
  displayableManagerCollection = vtk.vtkCollection()
  sliceView.getDisplayableManagers(displayableManagerCollection)
  for dmIndex in range(displayableManagerCollection.GetNumberOfItems()):
    displayableManager = displayableManagerCollection.GetItemAsObject(dmIndex)
    if not displayableManager.IsA("vtkMRMLRulerDisplayableManager"):
      continue
    displayableManager.RemoveAllRulerScalePresets()
    displayableManager.AddRulerScalePreset(   0.001, 5, 2, "nm", 1000.0)
    displayableManager.AddRulerScalePreset(   0.010, 5, 2, "nm", 1000.0)
    displayableManager.AddRulerScalePreset(   0.100, 5, 2, "nm", 1000.0)
    displayableManager.AddRulerScalePreset(   0.500, 5, 1, "nm", 1000.0)
    displayableManager.AddRulerScalePreset(   1.0,   5, 2, "um",    1.0)
    displayableManager.AddRulerScalePreset(   5.0,   5, 1, "um",    1.0)
    displayableManager.AddRulerScalePreset(  10.0,   5, 2, "um",    1.0)
    displayableManager.AddRulerScalePreset(  50.0,   5, 1, "um",    1.0)
    displayableManager.AddRulerScalePreset( 100.0,   5, 2, "um",    1.0)
    displayableManager.AddRulerScalePreset( 500.0,   5, 1, "um",    1.0)
    displayableManager.AddRulerScalePreset(1000.0,   5, 2, "mm",    0.001)

Show a slice view outside the view layout

layoutName = "TestSlice"
layoutLabel = "TS"
# ownerNode manages this view instead of the layout manager (it can be any node in the scene)
viewOwnerNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScriptedModuleNode")

# Create MRML nodes
viewNode = slicer.vtkMRMLSliceNode()
viewNode.SetName(layoutName)
viewNode.SetLayoutName(layoutName)
viewNode.SetLayoutLabel(layoutLabel)
viewNode.SetLayoutColor(1, 1, 0)
viewNode.SetAndObserveParentLayoutNodeID(viewOwnerNode.GetID())
viewNode = slicer.mrmlScene.AddNode(viewNode)
sliceCompositeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSliceCompositeNode")
sliceCompositeNode.SetLayoutName(layoutName)

# Create widget
viewWidget = slicer.qMRMLSliceWidget()
viewWidget.sliceViewName = layoutName
viewWidget.sliceViewLabel = layoutLabel
c = viewNode.GetLayoutColor()
viewWidget.sliceViewColor = qt.QColor.fromRgbF(c[0],c[1],c[2])
viewWidget.setMRMLScene(slicer.mrmlScene)
viewWidget.setMRMLSliceNode(viewNode)
viewWidget.show()

Show a 3D view outside the view layout

layoutName = "Test3DView"
layoutLabel = "T3"
# ownerNode manages this view instead of the layout manager (it can be any node in the scene)
viewOwnerNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScriptedModuleNode")

# Create MRML node
viewNode = slicer.vtkMRMLViewNode()
viewNode.SetName(layoutName)
viewNode.SetLayoutName(layoutName)
viewNode.SetLayoutLabel(layoutLabel)
viewNode.SetLayoutColor(1, 1, 0)
viewNode.SetAndObserveParentLayoutNodeID(viewOwnerNode.GetID())
viewNode = slicer.mrmlScene.AddNode(viewNode)

# Create widget
viewWidget = slicer.qMRMLThreeDWidget()
viewWidget.viewLabel = layoutLabel
viewWidget.viewColor = qt.QColor.fromRgbF(c[0],c[1],c[2])
viewWidget.setMRMLScene(slicer.mrmlScene)
viewWidget.setMRMLViewNode(viewNode)
viewWidget.show()

Get displayable manager of a certain type for a certain view

modelDisplayableManager = None
threeDViewWidget = slicer.app.layoutManager().threeDWidget(0)
managers = vtk.vtkCollection()
threeDViewWidget.getDisplayableManagers(managers)
for i in range(managers.GetNumberOfItems()):
  obj = managers.GetItemAsObject(i)
  if obj.IsA('vtkMRMLModelDisplayableManager'):
    modelDisplayableManager = obj
    break
if modelDisplayableManager is None:
  logging.error('Failed to find the model displayable manager')
  return

Running an ITK filter in Python using SimpleITK

Open the "Sample Data" module and download "MR Head", then paste the following snippet in Python interactor:

import SampleData
import SimpleITK as sitk
import sitkUtils

# Get input volume node
inputVolumeNode = SampleData.SampleDataLogic().downloadMRHead()
# Create new volume node for output
outputVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLScalarVolumeNode', 'MRHeadFiltered')

# Run processing
inputImage = sitkUtils.PullVolumeFromSlicer(inputVolumeNode)
filter = sitk.SignedMaurerDistanceMapImageFilter()
outputImage = filter.Execute(inputImage)
sitkUtils.PushVolumeToSlicer(outputImage, outputVolumeNode)

# Show processing result
slicer.util.setSliceViewerLayers(background=outputVolumeNode)

More information:

Get current mouse coordinates in a slice view

You can get 3D (RAS) coordinates of the current mouse cursor from the crosshair singleton node as shown in the example below:

def onMouseMoved(observer,eventid):  
  ras=[0,0,0]
  crosshairNode.GetCursorPositionRAS(ras)
  print(ras)

crosshairNode=slicer.util.getNode('Crosshair') 
crosshairNode.AddObserver(slicer.vtkMRMLCrosshairNode.CursorPositionModifiedEvent, onMouseMoved)

Get DataProbe text

You can get the mouse location in pixel coordinates along with the pixel value at the mouse by hitting the '.' (period) key in a slice view after pasting in the following code.

def printDataProbe():
  infoWidget = slicer.modules.DataProbeInstance.infoWidget
  for layer in ('B', 'F', 'L'):
    print(infoWidget.layerNames[layer].text, infoWidget.layerIJKs[layer].text, infoWidget.layerValues[layer].text)

s = qt.QShortcut(qt.QKeySequence('.'), mainWindow())
s.connect('activated()', printDataProbe)

Get reformatted image from a slice viewer as numpy array

Set up 'red' slice viewer to show thick slab reconstructed from 3 slices:

sliceNodeID = 'vtkMRMLSliceNodeRed'

# Get image data from slice view
sliceNode = slicer.mrmlScene.GetNodeByID(sliceNodeID)
appLogic = slicer.app.applicationLogic()
sliceLogic = appLogic.GetSliceLogic(sliceNode)
sliceLayerLogic = sliceLogic.GetBackgroundLayer()
reslice = sliceLayerLogic.GetReslice()
reslicedImage = vtk.vtkImageData()
reslicedImage.DeepCopy(reslice.GetOutput())

# Create new volume node using resliced image
volumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode")
volumeNode.SetIJKToRASMatrix(sliceNode.GetXYToRAS())
volumeNode.SetAndObserveImageData(reslicedImage)
volumeNode.CreateDefaultDisplayNodes()
volumeNode.CreateDefaultStorageNode()

# Get voxels as a numpy array
voxels = slicer.util.arrayFromVolume(volumeNode)
print voxels.shape

Combine multiple volumes into one

This example combines two volumes into a new one by subtracting one from the other.

import SampleData
[input1Volume, input2Volume] = SampleData.SampleDataLogic().downloadDentalSurgery()

import slicer.util
a = slicer.util.arrayFromVolume(input1Volume)
b = slicer.util.arrayFromVolume(input2Volume)

# 'a' and 'b' are numpy arrays,
# they can be combined using any numpy array operations
# to produce the result array 'c'
c = b-a

volumeNode = slicer.modules.volumes.logic().CloneVolume(input1Volume, "Difference")
slicer.util.updateVolumeFromArray(volumeNode, c)
setSliceViewerLayers(background=volumeNode)

Thick slab reconstruction and maximum/minimum intensity volume projections

Set up 'red' slice viewer to show thick slab reconstructed from 3 slices:

sliceNode = slicer.mrmlScene.GetNodeByID('vtkMRMLSliceNodeRed')
appLogic = slicer.app.applicationLogic()
sliceLogic = appLogic.GetSliceLogic(sliceNode)
sliceLayerLogic = sliceLogic.GetBackgroundLayer()
reslice = sliceLayerLogic.GetReslice()
reslice.SetSlabModeToMean()
reslice.SetSlabNumberOfSlices(10) # mean of 10 slices will computed
reslice.SetSlabSliceSpacingFraction(0.3) # spacing between each slice is 0.3 pixel (total 10 * 0.3 = 3 pixel neighborhood)
sliceNode.Modified()

Set up 'red' slice viewer to show maximum intensity projection (MIP):

sliceNode = slicer.mrmlScene.GetNodeByID('vtkMRMLSliceNodeRed')
appLogic = slicer.app.applicationLogic()
sliceLogic = appLogic.GetSliceLogic(sliceNode)
sliceLayerLogic = sliceLogic.GetBackgroundLayer()
reslice = sliceLayerLogic.GetReslice()
reslice.SetSlabModeToMax()
reslice.SetSlabNumberOfSlices(600) # use a large number of slices (600) to cover the entire volume
reslice.SetSlabSliceSpacingFraction(0.5) # spacing between slices are 0.5 pixel (supersampling is useful to reduce interpolation artifacts)
sliceNode.Modified()

The projected image is available in a vtkImageData object by calling reslice.GetOutput().

Change default file type for nodes (that have never been saved yet)

Default node can be specified that will be used as a basis of all new storage nodes. This can be used for setting default file extension. For example, change file format to STL for model nodes:

defaultModelStorageNode = slicer.vtkMRMLModelStorageNode()
defaultModelStorageNode.SetDefaultWriteFileExtension('stl')
slicer.mrmlScene.AddDefaultNode(defaultModelStorageNode)

To permanently change default file extension on your computer, copy-paste the code above into your application startup script (you can find its location in menu: Edit / Application settings / General / Application startup script).

Change file type for saving for all volumes (with already existing storage nodes)

If it is not necessary to preserve file paths then the simplest is to configure default storage node (as shown in the example above), then delete all existing storage nodes. When save dialog is opened, default storage nodes will be recreated.

# Delete existing model storage nodes so that they will be recreated with default settings
existingModelStorageNodes = slicer.util.getNodesByClass('vtkMRMLModelStorageNode')
for modelStorageNode in existingModelStorageNodes:
  slicer.mrmlScene.RemoveNode(modelStorageNode)

To update existing storage nodes to use new file extension (but keep all other parameters unchanged) you can use this approach (example is for volume storage):

requiredFileExtension = '.nia'
originalFileExtension = '.nrrd'
volumeNodes = slicer.util.getNodesByClass('vtkMRMLScalarVolumeNode')
for volumeNode in volumeNodes:
  volumeStorageNode = volumeNode.GetStorageNode()
  if not volumeStorageNode:
    volumeNode.AddDefaultStorageNode()
    volumeStorageNode = volumeNode.GetStorageNode()
    volumeStorageNode.SetFileName(volumeNode.GetName()+requiredFileExtension)
  else:
    volumeStorageNode.SetFileName(volumeStorageNode.GetFileName().replace(originalFileExtension, requiredFileExtension))

To set all volume nodes to save uncompressed by default (add this to .slicerrc.py so it takes effect for the whole session):

#set the default volume storage to not compress by default
defaultVolumeStorageNode = slicer.vtkMRMLVolumeArchetypeStorageNode()
defaultVolumeStorageNode.SetUseCompression(0)
slicer.mrmlScene.AddDefaultNode(defaultVolumeStorageNode)
logging.info("Volume nodes will be stored uncompressed by default")

Same thing as above, but applied to all segmentations instead of volumes:

#set the default volume storage to not compress by default
defaultVolumeStorageNode = slicer.vtkMRMLSegmentationStorageNode()
defaultVolumeStorageNode.SetUseCompression(0)
slicer.mrmlScene.AddDefaultNode(defaultVolumeStorageNode)
logging.info("Segmentation nodes will be stored uncompressed 

Sequences

Concatenate all sequences in the scene into a new sequence

# Get all sequence nodes in the scene
sequenceNodes = slicer.util.getNodesByClass('vtkMRMLSequenceNode')
mergedSequenceNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSequenceNode', 'Merged sequence')

# Merge all sequence nodes into a new sequence node
mergedIndexValue = 0
for sequenceNode in sequenceNodes:
    for itemIndex in range(sequenceNode.GetNumberOfDataNodes()):
        dataNode = sequenceNode.GetNthDataNode(itemIndex)
        mergedSequenceNode.SetDataNodeAtValue(dataNode, str(mergedIndexValue))
        mergedIndexValue += 1
    # Delete the sequence node we copied the data from, to prevent sharing of the same
    # node by multiple sequences
    slicer.mrmlScene.RemoveNode(sequenceNode)

# Create a sequence browser node for the new merged sequence
mergedSequenceBrowserNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSequenceBrowserNode', 'Merged')
mergedSequenceBrowserNode.AddSynchronizedSequenceNode(mergedSequenceNode)
slicer.modules.sequencebrowser.setToolBarActiveBrowserNode(mergedSequenceBrowserNode)
# Show proxy node in slice viewers
mergedProxyNode = mergedSequenceBrowserNode.GetProxyNode(mergedSequenceNode)
slicer.util.setSliceViewerLayers(background=mergedProxyNode)

Segmentations

Create a segmentation from a labelmap volume and display in 3D

labelmapVolumeNode = getNode('label')
seg = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSegmentationNode')
slicer.modules.segmentations.logic().ImportLabelmapToSegmentationNode(labelmapVolumeNode, seg)
seg.CreateClosedSurfaceRepresentation()
slicer.mrmlScene.RemoveNode(labelmapVolumeNode)

The last line is optional. It removes the original labelmap volume so that the same information is not shown twice.

Export labelmap node from segmentation node

Export smallest possible labelmap:

seg = getNode('Segmentation')
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')
slicer.modules.segmentations.logic().ExportAllSegmentsToLabelmapNode(seg, labelmapVolumeNode)

Export labelmap that matches geometry of a chosen reference volume:

seg = getNode('Segmentation')
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')
slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, referenceVolumeNode)

Export by pressing Ctrl+Shift+s key:

outputPath = "c:/tmp"

def exportLabelmap():
    segmentationNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLSegmentationNode")
    referenceVolumeNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLScalarVolumeNode")
    labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')
    slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, referenceVolumeNode)
    filepath = outputPath + "/" + referenceVolumeNode.GetName()+"-label.nrrd"
    slicer.util.saveNode(labelmapVolumeNode, filepath)
    slicer.mrmlScene.RemoveNode(labelmapVolumeNode.GetDisplayNode().GetColorNode())
    slicer.mrmlScene.RemoveNode(labelmapVolumeNode)
    slicer.util.delayDisplay("Segmentation saved to "+filepath)

shortcut = qt.QShortcut(slicer.util.mainWindow())
shortcut.setKey(qt.QKeySequence('Ctrl+Shift+s'))
shortcut.connect( 'activated()', exportLabelmap)

Export model nodes from segmentation node

segmentationNode = getNode("Segmentation")
shNode = slicer.mrmlScene.GetSubjectHierarchyNode()
exportFolderItemId = shNode.CreateFolderItem(shNode.GetSceneItemID(), "Segments")
slicer.modules.segmentations.logic().ExportAllSegmentsToModels(segmentationNode, exportFolderItemId)

Show a segmentation in 3D

Segmentation can only be shown in 3D if closed surface representation (or other 3D-displayable representation) is available. To create closed surface representation:

segmentation.CreateClosedSurfaceRepresentation()

Get a representation of a segment

Access binary labelmap stored in a segmentation node (without exporting it to a volume node) - if it does not exist, it will return None:

image = segmentationNode.GetBinaryLabelmapRepresentation(segmentID)

Get closed surface, if it does not exist, it will return None:

polydata = segmentationNode.GetClosedSurfaceRepresentation(segmentID)

Get binary labelmap representation. If it does not exist then it will be created for that single segment. Applies parent transforms by default (if not desired, another argument needs to be added to the end: false):

import vtkSegmentationCorePython as vtkSegmentationCore
outputOrientedImageData = vtkSegmentationCore.vtkOrientedImageData()
slicer.vtkSlicerSegmentationsModuleLogic.GetSegmentBinaryLabelmapRepresentation(segmentationNode, segmentID, outputOrientedImageData)

Same as above, for closed surface representation:

outputPolyData = vtk.vtkPolyData()
slicer.vtkSlicerSegmentationsModuleLogic.GetSegmentClosedSurfaceRepresentation(segmentationNode, segmentID, outputPolyData)

Convert all segments using default path and conversion parameters

segmentationNode.CreateBinaryLabelmapRepresentation()

Convert all segments using custom path or conversion parameters

Change reference image geometry parameter based on an existing referenceImageData image:

import vtkSegmentationCorePython as vtkSegmentationCore
referenceGeometry = vtkSegmentationCore.vtkSegmentationConverter.SerializeImageGeometry(referenceImageData)
segmentation.SetConversionParameter(vtkSegmentationCore.vtkSegmentationConverter.GetReferenceImageGeometryParameterName(), referenceGeometry)

Re-convert using a modified conversion parameter

Changing smoothing factor for closed surface generation:

import vtkSegmentationCorePython as vtkSegmentationCore
segmentation = getNode('Segmentation').GetSegmentation()

# Turn of surface smoothing
segmentation.SetConversionParameter('Smoothing factor','0.0')

# Recreate representation using modified parameters (and default conversion path)
segmentation.RemoveRepresentation(vtkSegmentationCore.vtkSegmentationConverter.GetSegmentationClosedSurfaceRepresentationName())
segmentation.CreateRepresentation(vtkSegmentationCore.vtkSegmentationConverter.GetSegmentationClosedSurfaceRepresentationName())

Get centroid of a segment in world (RAS) coordinates

This example shows how to get centroid of a segment in world coordinates and show that position in all slice views.

segmentationNode = getNode('Segmentation')
segmentId = 'Segment_1'

# Get array voxel coordinates
import numpy as np
seg=arrayFromSegment(segmentation_node, segmentId)
# numpy array has voxel coordinates in reverse order (KJI instead of IJK)
# and the array is cropped to minimum size in the segmentation
mean_KjiCropped = [coords.mean() for coords in np.nonzero(seg)]

# Get segmentation voxel coordinates
segImage = segmentationNode.GetBinaryLabelmapRepresentation(segmentId)
segImageExtent = segImage.GetExtent()
# origin of the array in voxel coordinates is determined by the start extent
mean_Ijk = [mean_KjiCropped[2], mean_KjiCropped[1], mean_KjiCropped[0]] + np.array([segImageExtent[0], segImageExtent[2], segImageExtent[4]])

# Get segmentation physical coordinates
ijkToWorld = vtk.vtkMatrix4x4()
segImage.GetImageToWorldMatrix(ijkToWorld)
mean_World = [0, 0, 0, 1]
ijkToRas.MultiplyPoint(np.append(mean_Ijk,1.0), mean_World)
mean_World = mean_World[0:3]

# If segmentation node is transformed, apply that transform to get RAS coordinates
transformWorldToRas = vtk.vtkGeneralTransform()
slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(segmentationNode.GetParentTransformNode(), None, transformWorldToRas)
mean_Ras = transformWorldToRas.TransformPoint(mean_World)

# Show mean position value and jump to it in all slice viewers
print(mean_Ras)
slicer.modules.markups.logic().JumpSlicesToLocation(mean_Ras[0], mean_Ras[1], mean_Ras[2], True)

How to run segment editor effects from a script

Editor effects are complex because they need to handle changing master volumes, undo/redo, masking operations, etc. Therefore, instead of using a segment editor effect, it is simpler to run the underlying filters directly from script.

This example demonstrates how to use Segment editor effects (without GUI, using qMRMLSegmentEditorWidget):

This example shows how to perform operations on segmentations using VTK filters:

Quantifying segments

Get size, position, and orientation of each segment

This example computes oriented bounding box for each segment and displays them using annotation ROI.

segmentationNode = getNode('Segmentation')

# Compute bounding boxes
import SegmentStatistics
segStatLogic = SegmentStatistics.SegmentStatisticsLogic()
segStatLogic.getParameterNode().SetParameter("Segmentation", segmentationNode.GetID())
segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_origin_ras.enabled",str(True))
segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_diameter_mm.enabled",str(True))
segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_direction_ras_x.enabled",str(True))
segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_direction_ras_y.enabled",str(True))
segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_direction_ras_z.enabled",str(True))
segStatLogic.computeStatistics()
stats = segStatLogic.getStatistics()

# Draw ROI for each oriented bounding box
import numpy as np
for segmentId in stats['SegmentIDs']:
    # Get bounding box
    obb_origin_ras = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_origin_ras"])
    obb_diameter_mm = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_diameter_mm"])
    obb_direction_ras_x = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_direction_ras_x"])
    obb_direction_ras_y = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_direction_ras_y"])
    obb_direction_ras_z = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_direction_ras_z"])
    # Create ROI
    segment = segmentationNode.GetSegmentation().GetSegment(segmentId)
    roi=slicer.mrmlScene.AddNewNodeByClass("vtkMRMLAnnotationROINode")
    roi.SetName(segment.GetName()+' bounding box')
    roi.SetXYZ(0.0, 0.0, 0.0)
    roi.SetRadiusXYZ(*(0.5*obb_diameter_mm))
    # Position and orient ROI using a transform
    obb_center_ras = obb_origin_ras+0.5*(obb_diameter_mm[0] * obb_direction_ras_x + obb_diameter_mm[1] * obb_direction_ras_y + obb_diameter_mm[2] * obb_direction_ras_z)
    boundingBoxToRasTransform = np.row_stack((np.column_stack((obb_direction_ras_x, obb_direction_ras_y, obb_direction_ras_z, obb_center_ras)), (0, 0, 0, 1)))
    boundingBoxToRasTransformMatrix = slicer.util.vtkMatrixFromArray(boundingBoxToRasTransform)
    transformNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLTransformNode')
    transformNode.SetAndObserveMatrixTransformToParent(boundingBoxToRasTransformMatrix)
    roi.SetAndObserveTransformNodeID(transformNode.GetID())

Accessing views, renderers, and cameras

Iterate through all 3D views in current layout:

layoutManager = slicer.app.layoutManager()
for threeDViewIndex in range(layoutManager.threeDViewCount) :
  view = layoutManager.threeDWidget(threeDViewIndex).threeDView()
  threeDViewNode = view.mrmlViewNode()
  cameraNode = slicer.modules.cameras.logic().GetViewActiveCameraNode(threeDViewNode)
  print('View node for 3D widget ' + str(threeDViewIndex))
  print('  Name: ' + threeDViewNode .GetName())
  print('  ID: ' + threeDViewNode .GetID())
  print('  Camera ID: ' + cameraNode.GetID())

Iterate through all slice views in current layout:

layoutManager = slicer.app.layoutManager()
for sliceViewName in layoutManager.sliceViewNames():
  view = layoutManager.sliceWidget(sliceViewName).sliceView()
  sliceNode = view.mrmlSliceNode()
  sliceLogic = slicer.app.applicationLogic().GetSliceLogic(sliceNode)
  compositeNode = sliceLogic.GetSliceCompositeNode()
  print('Slice view ' + str(sliceViewName))
  print('  Name: ' + sliceNode.GetName())
  print('  ID: ' + sliceNode.GetID())
  print('  Background volume: {0}'.format(compositeNode.GetBackgroundVolumeID()))
  print('  Foreground volume: {0} (opacity: {1})'.format(compositeNode.GetForegroundVolumeID(), compositeNode.GetForegroundOpacity()))
  print('  Label volume: {0} (opacity: {1})'.format(compositeNode.GetLabelVolumeID(), compositeNode.GetLabelOpacity()))

For low-level manipulation of views, it is possible to access VTK render windows, renderers and cameras of views in the current layout.

renderWindow = view.renderWindow()
renderers = renderWindow.GetRenderers()
renderer = renderers.GetItemAsObject(0)
camera = cameraNode.GetCamera()

Hide view controller bars

slicer.app.layoutManager().threeDWidget(0).threeDController().setVisible(False)
slicer.app.layoutManager().sliceWidget('Red').sliceController().setVisible(False)
slicer.app.layoutManager().plotWidget(0).plotController().setVisible(False)
slicer.app.layoutManager().tableWidget(0).tableController().setVisible(False)

Customize widgets in view controller bars

sliceController = slicer.app.layoutManager().sliceWidget("Red").sliceController()

# hide what is not needed
sliceController.pinButton().hide()
#sliceController.viewLabel().hide()
sliceController.fitToWindowToolButton().hide()
sliceController.sliceOffsetSlider().hide()

# add custom widgets
myButton = qt.QPushButton("My custom button")
sliceController.barLayout().addWidget(b)

Change 3D view background color

viewNode = slicer.app.layoutManager().threeDWidget(0).mrmlViewNode()
viewNode.SetBackgroundColor(1,0,0)
viewNode.SetBackgroundColor2(1,0,0)

Hide Slicer logo from main window (to increase space)

slicer.util.findChild(slicer.util.mainWindow(), 'LogoLabel').visible = False

Subject hierarchy

Get the pseudo-singleton subject hierarchy node

It manages the whole hierarchy and provides functions to access and manipulate

 shNode = slicer.mrmlScene.GetSubjectHierarchyNode()

Create subject hierarchy item

 # If it is for a data node, it is automatically created, but the create function can be used to set parent:
 shNode.CreateItem(parentItemID, dataNode)
 # If it is a hierarchy item without a data node, then the create function must be used:
 shNode.CreateSubjectItem(parentItemID, name)
 shNode.CreateFolderItem(parentItemID, name)
 shNode.CreateHierarchyItem(parentItemID, name, level) # Advanced method to set level attribute manually (usually subject, study, or folder, but it can be a virtual branch for example)

Get subject hierarchy item

Items in subject hierarchy are uniquely identified by integer IDs

 # Get scene item ID first because it is the root item:
 sceneItemID = shNode.GetSceneItemID()
 # Get direct child by name
 subjectItemID = shNode.GetItemChildWithName(sceneItemID, 'Subject_1')
 # Get item for data node
 itemID = shNode.GetItemByDataNode(dataNode)
 # Get item by UID (such as DICOM)
 itemID = shNode.GetItemByUID(slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMUIDName(), seriesInstanceUid)
 itemID = shNode.GetItemByUIDList(slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMInstanceUIDName(), instanceUID)
 # Invalid item ID for checking validity of a given ID (most functions return the invalid ID when item is not found)
 invalidItemID = slicer.vtkMRMLSubjectHierarchyNode.GetInvalidItemID()

Traverse children of a subject hierarchy item

 children = vtk.vtkIdList()
 shNode.GetItemChildren(parent, children)
 for i in range(children.GetNumberOfIds()):
   child = children.GetId(i)
   ...

Manipulate subject hierarchy item

Instead of node operations on the individual subject hierarchy nodes, item operations are performed on the one subject hierarchy node.

 # Set item name
 shNode.SetItemName(itemID, 'NewName')
 # Set item parent (reparent)
 shNode.SetItemParent(itemID, newParentItemID)
 # Set visibility of data node associated to an item
 shNode.SetItemDisplayVisibility(itemID, 1)
 # Set visibility of whole branch
 # Note: Folder-type items (fodler, subject, study, etc.) create their own display nodes when show/hiding from UI.
 #       The displayable managers use SH information to determine visibility of an item, so no need to show/hide individual leaf nodes any more.
 #       Once the folder display node is created, it can be shown hidden simply using shNode.SetItemDisplayVisibility
 # From python, this is how to trigger creating a folder display node
 pluginHandler = slicer.qSlicerSubjectHierarchyPluginHandler().instance()
 folderPlugin = pluginHandler.pluginByName('Folder')
 folderPlugin.setDisplayVisibility(folderItemID, 1)

Filter items in TreeView or ComboBox

Displayed items can be filtered using setAttributeFilter method. An example of the usage can be found in the unit test. Modified version here:

   print(shTreeView.displayedItemCount()) # 5
   shTreeView.setAttributeFilter('DICOM.Modality') # Nodes must have this attribute
   print(shTreeView.displayedItemCount()) # 3
   shTreeView.setAttributeFilter('DICOM.Modality','CT') # Have attribute and equal 'CT'
   print(shTreeView.displayedItemCount()) # 1
   shTreeView.removeAttributeFilter()
   print(shTreeView.displayedItemCount()) # 5

Listen to subject hierarchy item events

The subject hierarchy node sends the node item id as calldata. Item IDs are vtkIdType, which are NOT vtkObjects. You need to use vtk.calldata_type(vtk.VTK_LONG) (otherwise the application crashes).

 class MyListenerClass(VTKObservationMixin):
   def __init__(self):
     VTKObservationMixin.__init__(self)
     
     shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)
     self.addObserver(shNode, shNode.SubjectHierarchyItemModifiedEvent, self.shItemModifiedEvent)
    
   @vtk.calldata_type(vtk.VTK_LONG) 
   def shItemModifiedEvent(self, caller, eventId, callData):
     print("SH Node modified")
     print("SH item ID: {0}".format(callData))

Use whitelist to customize view menu

When right-clicking certain types of nodes in the 2D/3D views, a subject hierarchy menu pops up. If menu actions need to be removed, a whitelist can be used to specify the ones that should show up.

 pluginHandler = slicer.qSlicerSubjectHierarchyPluginHandler.instance()
 pluginLogic = pluginHandler.pluginLogic()
 menuActions = pluginLogic.availableViewMenuActionNames()
 # Returns ('RenamePointAction', 'DeletePointAction', 'ToggleSelectPointAction', 'EditPropertiesAction')
 newActions = ['RenamePointAction']
 pluginLogic.setDisplayedViewMenuActionNames(newActions)

Plotting

Slicer plots displayed in view layout

Create histogram plot of a volume and show it embedded in the view layout. More information: https://www.slicer.org/wiki/Documentation/Nightly/Developers/Plots

Using slicer.util.plot utility function

# Get a volume from SampleData and compute its histogram
import SampleData
import numpy as np
volumeNode = SampleData.SampleDataLogic().downloadMRHead()
histogram = np.histogram(arrayFromVolume(volumeNode), bins=50)

chartNode = slicer.util.plot(histogram, xColumnIndex = 1)
chartNode.SetYAxisRangeAuto(False)
chartNode.SetYAxisRange(0, 4e5)

SlicerPlot.png

Using MRML classes only

# Get a volume from SampleData
import SampleData
volumeNode = SampleData.SampleDataLogic().downloadMRHead()

# Compute histogram values
import numpy as np
histogram = np.histogram(arrayFromVolume(volumeNode), bins=50)

# Save results to a new table node
tableNode=slicer.mrmlScene.AddNewNodeByClass("vtkMRMLTableNode")
updateTableFromArray(tableNode, histogram)
tableNode.GetTable().GetColumn(0).SetName("Count")
tableNode.GetTable().GetColumn(1).SetName("Intensity")

# Create plot
plotSeriesNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLPlotSeriesNode", volumeNode.GetName() + ' histogram')
plotSeriesNode.SetAndObserveTableNodeID(tableNode.GetID())
plotSeriesNode.SetXColumnName("Intensity")
plotSeriesNode.SetYColumnName("Count")
plotSeriesNode.SetPlotType(plotSeriesNode.PlotTypeScatterBar)
plotSeriesNode.SetColor(0, 0.6, 1.0)

# Create chart and add plot
plotChartNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLPlotChartNode")
plotChartNode.AddAndObservePlotSeriesNodeID(plotSeriesNode.GetID())
plotChartNode.YAxisRangeAutoOff()
plotChartNode.SetYAxisRange(0, 500000)

# Show plot in layout
slicer.modules.plots.logic().ShowChartInLayout(plotChartNode)

Using matplotlib

Matplotlib may be used from within Slicer, but the default Tk backend locks up and crashes Slicer. However, Matplotlib may still be used through other backends. More details can be found on the MatPlotLib pages.

Non-interactive plot

try:
  import matplotlib
except ModuleNotFoundError:
  pip_install('matplotlib')
  import matplotlib

matplotlib.use('Agg')
from pylab import *

t1 = arange(0.0, 5.0, 0.1)
t2 = arange(0.0, 5.0, 0.02)
t3 = arange(0.0, 2.0, 0.01) 

subplot(211)
plot(t1, cos(2*pi*t1)*exp(-t1), 'bo', t2, cos(2*pi*t2)*exp(-t2), 'k')
grid(True)
title('A tale of 2 subplots')
ylabel('Damped')

subplot(212)
plot(t3, cos(2*pi*t3), 'r--')
grid(True)
xlabel('time (s)')
ylabel('Undamped')
savefig('MatplotlibExample.png')

# Static image view
pm = qt.QPixmap("MatplotlibExample.png")
imageWidget = qt.QLabel()
imageWidget.setPixmap(pm)
imageWidget.setScaledContents(True)
imageWidget.show()

MatplotlibExample.png

Plot in Slicer Jupyter notebook

try:
  import matplotlib
except ModuleNotFoundError:
  pip_install('matplotlib')
  import matplotlib

matplotlib.use('Agg')
from pylab import *

t1 = arange(0.0, 5.0, 0.1)
t2 = arange(0.0, 5.0, 0.02)
t3 = arange(0.0, 2.0, 0.01) 

subplot(211)
plot(t1, cos(2*pi*t1)*exp(-t1), 'bo', t2, cos(2*pi*t2)*exp(-t2), 'k')
grid(True)
title('A tale of 2 subplots')
ylabel('Damped')

subplot(212)
plot(t3, cos(2*pi*t3), 'r--')
grid(True)
xlabel('time (s)')
ylabel('Undamped')
savefig('MatplotlibExample.png')
display(filename='MatplotlibExample.png', type="image/png", binary=True)

JupyterNotebookMatplotlibExample.png

Interactive plot using wxWidgets GUI toolkit

try:
  import matplotlib
  import wx
except ModuleNotFoundError:
  pip_install('matplotlib wxPython')
  import matplotlib

# Get a volume from SampleData and compute its histogram
import SampleData
import numpy as np
volumeNode = SampleData.SampleDataLogic().downloadMRHead()
histogram = np.histogram(arrayFromVolume(volumeNode), bins=50)

# Set matplotlib to use WXAgg backend
import matplotlib
matplotlib.use('WXAgg')

# Show an interactive plot
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.plot(histogram[1][1:], histogram[0].astype(float))
ax.grid(True)
ax.set_ylim((0, 4e5))
plt.show(block=False)

InteractiveMatplotlibExample.png

Execute external applications

How to run external applications from Slicer.

Run process in default environment

When a process is launched from Slicer then by default Slicer's ITK, VTK, Qt, etc. libraries are used. If an external application has its own version of these libraries, then the application is expected to crash. To prevent crashing, the application must be run in the environment where Slicer started up (without all Slicer-specific library paths). This startup environment can be retrieved using slicer.util.startupEnvironment().

Example: run Python3 script from Slicer:

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

will output:

'hola\n'

On some systems, shell=True must be specified as well.

Manage extensions

Download and install extension

extensionName = 'SlicerIGT'
em = slicer.app.extensionsManagerModel()
if not em.isExtensionInstalled(extensionName):
    extensionMetaData = em.retrieveExtensionMetadataByName(extensionName)
    url = em.serverUrl().toString()+'/download/item/'+extensionMetaData['item_id']
    extensionPackageFilename = slicer.app.temporaryPath+'/'+extensionMetaData['md5']
    slicer.util.downloadFile(url, extensionPackageFilename)
    em.installExtension(extensionPackageFilename)
    slicer.util.restart()