Difference between revisions of "Documentation/Nightly/ScriptRepository"

From Slicer Wiki
Jump to: navigation, search
Tag: 2017 source edit
 
(131 intermediate revisions by 6 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=
  
 
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.
 
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.
 +
 +
==Get node object from the scene from node name or ID==
 +
 +
Examples in the script repository commonly use <code>slicer.util.getNode()</code> function for getting a node object from the scene. This method is only recommended for testing and interactive debugging.
 +
 +
*<code>slicer.util.getNode()</code> is recommended **only for interactive debugging** in the Python console/Jupyter notebook
 +
**its input is intentionally defined vaguely (it can be either node ID or name and you can use wildcards such as <code>*</code>), which is good because it make it simpler to use, but the uncertain behavior is not good for general-purpose use in a module
 +
**throws an exception so that the developer knows immediately that there was a typo or other unexpected error
 +
*<code>slicer.mrmlScene.GetNodeByID()</code> is optimized for usage in modules:
 +
**its behavior is more predictable: it only accepts node ID as input. <code>slicer.mrmlScene.GetFirstNodeByName()</code> can be used to get a node by its name, but since multiple nodes in the scene can have the same name, it is not recommended to keep reference to a node by its name.
 +
**if node is not found it returns <code>None</code> (instead of throwing an exception), because this is often not considered an error in module code (it is just used to check existence of a node) and using return value for not-found nodes allows simpler syntax
  
 
==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 56:
 
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 68:
 
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 75:
 
</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 89:
  
 
==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
  
 
==Load volume from file==
 
==Load volume from file==
When loading a volume from file, it is recommended to set returnNode=True to retrieve the loaded volume node.
+
 
 
<pre>
 
<pre>
 
loadedVolumeNode = slicer.util.loadVolume('c:/Users/abc/Documents/MRHead.nrrd')
 
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:
+
Additional options may be specified in <code>properties</code> argument. For example, load an image stack by disabling <code>singleFile</code> option:
 +
 
 +
<pre>
 +
loadedVolumeNode = slicer.util.loadVolume('c:/Users/abc/Documents/SomeImage/file001.png', {'singleFile': False})
 +
</pre>
 +
 
 +
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==
 
  
To show volume rendering of a volume automatically when it is loaded, add the lines below to your
+
==Load volume from URL==
[[Documentation/{{documentation/version}}/Developers/Python_scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F|.slicerrc file]].
+
 
 +
Download a volume from a URL and load it into the scene using the code snippet below. Downloaded data is temporarily preserved in the application's cache folder and if the checksum of the already downloaded data matches the specified checksum (<algo>:<digest>) then the file is retrieved from the cache instead of being downloaded again. To compute digest with algo ''SHA256'', you can run <code>slicer.util.computeChecksum("SHA256", "path/to/file")</code>.
  
 
<pre>
 
<pre>
@vtk.calldata_type(vtk.VTK_OBJECT)
+
import SampleData
def onNodeAdded(caller, event, calldata):
+
sampleDataLogic = SampleData.SampleDataLogic()
   node = calldata
+
loadedNodes = sampleDataLogic.downloadFromURL(
   if isinstance(node, slicer.vtkMRMLVolumeNode):
+
    nodeNames='MRHead',
 +
    fileNames='MR-head25.nrrd',
 +
    uris='https://github.com/Slicer/SlicerTestingData/releases/download/SHA256/cc211f0dfd9a05ca3841ce1141b292898b2dd2d3f08286affadf823a7e58df93',
 +
    checksums='SHA256:cc211f0dfd9a05ca3841ce1141b292898b2dd2d3f08286affadf823a7e58df93')[0]
 +
</pre>
 +
 
 +
With interruptible progress reporting using a progress bar:
 +
 
 +
<pre>
 +
import SampleData
 +
 
 +
def reportProgress(msg, level=None):
 +
    # Print progress in the console
 +
    print("Loading... {0}%".format(sampleDataLogic.downloadPercent))
 +
    # Abort download if cancel is clicked in progress bar
 +
    if slicer.progressWindow.wasCanceled:
 +
        raise Exception('download aborted')
 +
    # Update progress window
 +
    slicer.progressWindow.show()
 +
    slicer.progressWindow.activateWindow()
 +
    slicer.progressWindow.setValue(int(sampleDataLogic.downloadPercent))
 +
    slicer.progressWindow.setLabelText("Downloading...")
 +
    # Process events to allow screen to refresh
 +
    slicer.app.processEvents()
 +
 
 +
try:
 +
    volumeNode = None
 +
    slicer.progressWindow = slicer.util.createProgressDialog()
 +
    sampleDataLogic = SampleData.SampleDataLogic()
 +
    sampleDataLogic.logMessage = reportProgress
 +
    loadedNodes = sampleDataLogic.downloadFromURL(
 +
        nodeNames='MRHead',
 +
        fileNames='MR-head25.nrrd',
 +
        uris='https://github.com/Slicer/SlicerTestingData/releases/download/SHA256/cc211f0dfd9a05ca3841ce1141b292898b2dd2d3f08286affadf823a7e58df93',
 +
        checksums='SHA256:cc211f0dfd9a05ca3841ce1141b292898b2dd2d3f08286affadf823a7e58df93')
 +
    volumeNode = loadedNodes[0]
 +
finally:
 +
    slicer.progressWindow.close()
 +
</pre>
 +
 
 +
==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
 +
[[Documentation/{{documentation/version}}/Developers/Python_scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F|.slicerrc file]].
 +
 
 +
<pre>
 +
@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
 
     # Call showVolumeRendering using a timer instead of calling it directly
 
     # to allow the volume loading to fully complete.
 
     # to allow the volume loading to fully complete.
Line 115: Line 194:
 
</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 148: Line 227:
  
 
==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===
  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 ===
+
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.
  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:===
+
<pre>
 +
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))
 +
</pre>
 +
 
 +
===How to import DICOM files into the application's DICOM database===
 +
 
 +
This code snippet uses Slicer DICOM browser built-in indexer to import DICOM files into the database. Images are not loaded into the scene, but they show up in the DICOM browser. After import, data sets can be loaded using DICOMUtils functions (e.g., loadPatientByUID) - see above for an example.
 +
 
 +
<pre>
 +
# instantiate a new DICOM browser
 +
slicer.util.selectModule("DICOM")
 +
dicomBrowser = slicer.modules.DICOMWidget.browserWidget.dicomBrowser
 +
# use dicomBrowser.ImportDirectoryCopy to make a copy of the files (useful for importing data from removable storage)
 +
dicomBrowser.importDirectory(dicomFilesDirectory, dicomBrowser.ImportDirectoryAddLink)
 +
# wait for import to finish before proceeding (optional, if removed then import runs in the background)
 +
dicomBrowser.waitForImportFinished()
 +
</pre>
 +
 
 +
===How to import DICOM files using DICOMweb===
 +
 
 +
Download and import DICOM data set using DICOMweb from [https://kheops.online/ Kheops], Google Health API, etc.
 +
 
 +
How to obtain accessToken:
 +
 
 +
*Google Cloud: Execute <code>gcloud auth print-access-token</code> once you have logged in
 +
*Kheops: create an album, create a sharing link (somethin like <code>https://demo.kheops.online/view/TfYXwbKAW7JYbAgZ7MyISf</code>), the token is the string after the last slash
 +
 
 +
<pre>
 +
slicer.util.selectModule("DICOM")  # ensure DICOM database is initialized and
 +
slicer.app.processEvents()
 +
from DICOMLib import DICOMUtils
 +
DICOMUtils.importFromDICOMWeb(
 +
    dicomWebEndpoint="http://demo.kheops.online/api",
 +
    studyInstanceUID="1.3.6.1.4.1.14519.5.2.1.8421.4009.985792766370191766692237040819",
 +
    accessToken="TfYXwbKAW7JYbAgZ7MyISf")
 +
</pre>
 +
 
 +
===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:
 +
 
 +
<pre>
 +
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'))
 +
# Get tag group,number from dicom dictionary
 +
import pydicom as dicom
 +
tagName = "StudyDate"
 +
tagStr = str(dicom.tag.Tag(tagName))[1:-1].replace(' ','')
 +
print(db.fileValue(fileList[0],tagStr))
 +
</pre>
 +
 
 +
===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'
 
   volumeName='2: ENT IMRT'
 
   n=slicer.util.getNode(volumeName)
 
   n=slicer.util.getNode(volumeName)
Line 177: Line 315:
 
   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 186: Line 324:
 
   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 201: Line 339:
 
   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'); 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()"
Line 207: Line 345:
 
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 229: Line 367:
 
</pre>
 
</pre>
  
=== Customize table columns in DICOM browser ===
+
===Export a segmentation to DICOM segmentation object===
 +
 
 +
<pre>
 +
segmentationNode = ...
 +
referenceVolumeNode = ...
 +
outputFolder = "c:/tmp/dicom-output"
 +
 
 +
# Associate segmentation node with a reference volume node
 +
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)
 +
referenceVolumeShItem = shNode.GetItemByDataNode(referenceVolumeNode)
 +
studyShItem = shNode.GetItemParent(referenceVolumeShItem)
 +
segmentationShItem = shNode.GetItemByDataNode(segmentationNode)
 +
shNode.SetItemParent(segmentationShItem, studyShItem)
 +
 
 +
# Export to DICOM
 +
import DICOMSegmentationPlugin
 +
exporter = DICOMSegmentationPlugin.DICOMSegmentationPluginClass()
 +
exportables = exporter.examineForExport(segmentationShItem)
 +
for exp in exportables:
 +
    exp.directory = outputFolder
 +
 
 +
exporter.export(exportables)
 +
</pre>
 +
 
 +
===Customize table columns in DICOM browser===
 +
 
 +
Documentation of methods for changing DICOM browser columns: https://github.com/commontk/CTK/blob/master/Libs/DICOM/Core/ctkDICOMDatabase.h#L354-L375
  
 
<pre>
 
<pre>
 
# Get browser and database
 
# Get browser and database
dicomBrowser = slicer.modules.dicom.widgetRepresentation().self().dicomBrowser
+
dicomBrowser = slicer.modules.dicom.widgetRepresentation().self().browserWidget.dicomBrowser
dicomDatabase = dicomBrowser.database() # Need to go this way, do not use slicer.dicomDatabase for this
+
dicomDatabase = dicomBrowser.database()
 +
 
 +
# Print list of available columns
 +
print(dicomDatabase.patientFieldNames)
 +
print(dicomDatabase.studyFieldNames)
 +
print(dicomDatabase.seriesFieldNames)
  
 
# Change column order
 
# Change column order
Line 241: Line 410:
 
# Change column visibility
 
# Change column visibility
 
dicomDatabase.setVisibilityForField('Patients', 'PatientsBirthDate', False)
 
dicomDatabase.setVisibilityForField('Patients', 'PatientsBirthDate', False)
 +
dicomDatabase.setVisibilityForField('Patients', 'PatientsComments', True)
 +
dicomDatabase.setWeightForField('Patients', 'PatientsComments', 8)
 
# Change column name
 
# Change column name
 
dicomDatabase.setDisplayedNameForField('Series', 'DisplayedCount', 'Number of images')
 
dicomDatabase.setDisplayedNameForField('Series', 'DisplayedCount', 'Number of images')
 +
# Change column width to manual
 +
dicomDatabase.setFormatForField('Series', 'SeriesDescription', '{"resizeMode":"interactive"}')
 
# Customize table manager in DICOM browser
 
# Customize table manager in DICOM browser
 
dicomTableManager = dicomBrowser.dicomTableManager()
 
dicomTableManager = dicomBrowser.dicomTableManager()
 
dicomTableManager.selectionMode = qt.QAbstractItemView.SingleSelection
 
dicomTableManager.selectionMode = qt.QAbstractItemView.SingleSelection
 
dicomTableManager.autoSelectSeries = False
 
dicomTableManager.autoSelectSeries = False
 +
 +
# Force database views update
 +
dicomDatabase.closeDatabase()
 +
dicomDatabase.openDatabase(dicomBrowser.database().databaseFilename)
 +
</pre>
 +
 +
===Query and retrieve data from a PACS using classic DIMSE DICOM networking===
 +
 +
<pre>
 +
# Query
 +
dicomQuery = ctk.ctkDICOMQuery()
 +
dicomQuery.callingAETitle = "SLICER"
 +
dicomQuery.calledAETitle = "ANYAE"
 +
dicomQuery.host = "dicomserver.co.uk"
 +
dicomQuery.port = 11112
 +
dicomQuery.preferCGET = True
 +
dicomQuery.filters = {'Name':'Anon', 'Modalities':'MR'}
 +
# temporary in-memory database for storing query results
 +
tempDb = ctk.ctkDICOMDatabase()
 +
tempDb.openDatabase('')
 +
dicomQuery.query(tempDb)
 +
 +
# Retrieve
 +
dicomRetrieve = ctk.ctkDICOMRetrieve()
 +
dicomRetrieve.callingAETitle = dicomQuery.callingAETitle
 +
dicomRetrieve.calledAETitle = dicomQuery.calledAETitle
 +
dicomRetrieve.host = dicomQuery.host
 +
dicomRetrieve.port = dicomQuery.port
 +
dicomRetrieve.setMoveDestinationAETitle("SLICER");
 +
dicomRetrieve.setDatabase(slicer.dicomDatabase)
 +
for study in dicomQuery.studyInstanceUIDQueried:
 +
    print(f"ctkDICOMRetrieveTest2: Retrieving {study}")
 +
    slicer.app.processEvents()
 +
    if dicomQuery.preferCGET:
 +
        success = dicomRetrieve.getStudy(study)
 +
    else:
 +
        success = dicomRetrieve.moveStudy(study)
 +
    print(f"  - {'success' if success else 'failed'}")
 +
slicer.dicomDatabase.updateDisplayedFields()
 
</pre>
 
</pre>
  
 
==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 258: Line 471:
  
 
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.
 
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.
 +
 +
==Switch to a different module==
 +
 +
This utility function can be used to open a different module:
 +
 +
<pre>
 +
slicer.util.selectModule('DICOM')
 +
</pre>
  
 
==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 295: Line 517:
 
</pre>
 
</pre>
  
==Set slice position and orientation from 3 markup fiducials==
+
===Specify a sphere by multiple of markups points===
  
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.
+
Drop multiple markup points at the boundary of the spherical object and and copy-paste the code below into the Python console to get best-fit sphere. Minimum 4 points are required, it is recommended to place the points far from each other for most accurate fit.
  
 
<pre>
 
<pre>
# Update plane from fiducial points
+
# Get markup node from scene
def UpdateSlicePlane(param1=None, param2=None):
+
markups = slicer.util.getNode('F')
  # Get point positions as numpy array
+
 
  import numpy as np
+
from scipy.optimize import least_squares
  nOfFiduciallPoints = markups.GetNumberOfFiducials()
+
import numpy
  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
+
def fit_sphere_least_squares(x_values, y_values, z_values, initial_parameters, bounds=((-numpy.inf, -numpy.inf, -numpy.inf, -numpy.inf),(numpy.inf, numpy.inf, numpy.inf, numpy.inf))):
sliceNode = slicer.app.layoutManager().sliceWidget('Red').mrmlSliceNode()
+
    """
markups = slicer.util.getNode('F')
+
    Source: https://github.com/thompson318/scikit-surgery-sphere-fitting/blob/master/sksurgeryspherefitting/algorithms/sphere_fitting.py
 +
    Uses scipy's least squares optimisor to fit a sphere to a set
 +
    of 3D Points
 +
    :return: x: an array containing the four fitted parameters
 +
    :return: ier: int An integer flag. If it is equal to 1, 2, 3 or 4, the
 +
            solution was found.
 +
    :param: (x,y,z) three arrays of equal length containing the x, y, and z
 +
            coordinates.
 +
    :param: an array containing four initial values (centre, and radius)
 +
    """
 +
    return least_squares(_calculate_residual_sphere, initial_parameters, bounds=bounds, method='trf', jac='3-point', args=(x_values, y_values, z_values))
  
# Update slice plane manually
+
def _calculate_residual_sphere(parameters, x_values, y_values, z_values):
UpdateSlicePlane()
+
    """
 +
    Source: https://github.com/thompson318/scikit-surgery-sphere-fitting/blob/master/sksurgeryspherefitting/algorithms/sphere_fitting.py
 +
    Calculates the residual error for an x,y,z coordinates, fitted
 +
    to a sphere with centre and radius defined by the parameters tuple
 +
    :return: The residual error
 +
    :param: A tuple of the parameters to be optimised, should contain [x_centre, y_centre, z_centre, radius]
 +
    :param: arrays containing the x,y, and z coordinates.
 +
    """
 +
    #extract the parameters
 +
    x_centre, y_centre, z_centre, radius = parameters
 +
    #use numpy's sqrt function here, which works by element on arrays
 +
    distance_from_centre = numpy.sqrt((x_values - x_centre)**2 + (y_values - y_centre)**2 + (z_values - z_centre)**2)
 +
    return distance_from_centre - radius
  
# Update slice plane automatically whenever points are changed
+
# Fit a sphere to the markups fidicual points
markupObservation = [markups, markups.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, UpdateSlicePlane, 2)]
+
markupsPositions = slicer.util.arrayFromMarkupsControlPoints(markups)
</pre>
+
import numpy as np
 +
# initial guess
 +
center0 = np.mean(markupsPositions, 0)
 +
radius0 = np.linalg.norm(np.amin(markupsPositions,0)-np.amax(markupsPositions,0))/2.0
 +
fittingResult = fit_sphere_least_squares(markupsPositions[:,0], markupsPositions[:,1], markupsPositions[:,2], [center0[0], center0[1], center0[2], radius0])
 +
[centerX, centerY, centerZ, radius] = fittingResult['x']
 +
 
 +
# Create a sphere using the fitted parameters
 +
sphere = vtk.vtkSphereSource()
 +
sphere.SetPhiResolution(30)
 +
sphere.SetThetaResolution(30)
 +
sphere.SetCenter(centerX, centerY, centerZ)
 +
sphere.SetRadius(radius)
 +
sphere.Update()
 +
 +
# Add the sphere to the 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)
 +
</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.
  
To stop automatic updates, run this:
 
 
<pre>
 
<pre>
markupObservation[0].RemoveObserver(markupObservation[1])
+
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>
  
 +
==Measure angle between two markup planes==
 +
 +
Measure angle between two markup plane nodes. Whenever any of the plane nodes are moved, the updated angle is printed on the console.
 +
 +
<pre>
 +
planeNodeNames = ['P', 'P_1']
 +
 +
# Print angles between slice nodes
 +
def ShowAngle(unused1=None, unused2=None):
 +
    planeNormalVectors = []
 +
    for planeNodeName in planeNodeNames:
 +
        planeNode = slicer.util.getFirstNodeByClassByName('vtkMRMLMarkupsPlaneNode', planeNodeName)
 +
        planeNormalVector = [0.0, 0.0, 0.0]
 +
        planeNode.GetNormalWorld(planeNormalVector)
 +
        planeNormalVectors.append(planeNormalVector)
 +
    angleRad = vtk.vtkMath.AngleBetweenVectors(planeNormalVectors[0], planeNormalVectors[1])
 +
    angleDeg = vtk.vtkMath.DegreesFromRadians(angleRad)
 +
    print('Angle between planes {0} and {1} = {2:0.3f}'.format(planeNodeNames[0], planeNodeNames[1], angleDeg))
 +
 +
# Observe plane node changes
 +
for planeNodeName in planeNodeNames:
 +
    planeNode = slicer.util.getFirstNodeByClassByName('vtkMRMLMarkupsPlaneNode', planeNodeName)
 +
    planeNode.AddObserver(slicer.vtkMRMLMarkupsPlaneNode.PointModifiedEvent, ShowAngle)
 +
 +
# Print current angle
 +
ShowAngle()
 +
</pre>
  
==Set slice position and orientation from a normal vector and position==
+
==Measure angle between two markup lines==
  
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.
+
Measure angle between two markup line nodes. Whenever either line is moved, the updated angle is printed on the console.
  
 
<pre>
 
<pre>
def setSlicePoseFromSliceNormalAndPosition(sliceNode, sliceNormal, slicePosition, defaultViewUpDirection=None, backupViewRightDirection=None):
+
lineNodeNames = ['L', 'L_1']
     """
+
 
     Set slice pose from the provided plane normal and position. View up direction is determined automatically,
+
# Print angles between slice nodes
     to make view up point towards defaultViewUpDirection.
+
def ShowAngle(unused1=None, unused2=None):
    :param defaultViewUpDirection Slice view will be spinned in-plane to match point approximately this up direction. Default: patient superior.
+
     import numpy as np
    :param backupViewRightDirection Slice view will be spinned in-plane to match point approximately this right direction
+
     lineDirectionVectors = []
         if defaultViewUpDirection is too similar to sliceNormal. Default: patient left.
+
     for lineNodeName in lineNodeNames:
    """
+
         lineNode = slicer.util.getFirstNodeByClassByName('vtkMRMLMarkupsLineNode', lineNodeName)
    # Fix up input directions
+
        lineStartPos = np.zeros(3)
    if defaultViewUpDirection is None:
+
        lineEndPos = np.zeros(3)
         defaultViewUpDirection = [0,0,1]
+
         lineNode.GetNthControlPointPositionWorld(0, lineStartPos)
    if backupViewRightDirection is None:
+
         lineNode.GetNthControlPointPositionWorld(1, lineEndPos)
         backupViewRightDirection = [-1,0,0]
+
        lineDirectionVector = (lineEndPos-lineStartPos)/np.linalg.norm(lineEndPos-lineStartPos)
    if sliceNormal[1]>=0:
+
         lineDirectionVectors.append(lineDirectionVector)
         sliceNormalStandardized = sliceNormal
+
     angleRad = vtk.vtkMath.AngleBetweenVectors(lineDirectionVectors[0], lineDirectionVectors[1])
     else:
+
     angleDeg = vtk.vtkMath.DegreesFromRadians(angleRad)
        sliceNormalStandardized = [-sliceNormal[0], -sliceNormal[1], -sliceNormal[2]]
+
     print('Angle between lines {0} and {1} = {2:0.3f}'.format(lineNodeNames[0], lineNodeNames[1], angleDeg))
    # Compute slice axes
+
 
     sliceNormalViewUpAngle = vtk.vtkMath.AngleBetweenVectors(sliceNormalStandardized, defaultViewUpDirection)
+
# Observe line node changes
     angleTooSmallThresholdRad = 0.25 # about 15 degrees
+
for lineNodeName in lineNodeNames:
    if sliceNormalViewUpAngle > angleTooSmallThresholdRad and sliceNormalViewUpAngle < vtk.vtkMath.Pi() - angleTooSmallThresholdRad:
+
    lineNode = slicer.util.getFirstNodeByClassByName('vtkMRMLMarkupsLineNode', lineNodeName)
        viewUpDirection = defaultViewUpDirection
+
     lineNode.AddObserver(slicer.vtkMRMLMarkupsLineNode.PointModifiedEvent, ShowAngle)
        sliceAxisY = viewUpDirection
+
 
        sliceAxisX = [0, 0, 0]
+
# Print current angle
        vtk.vtkMath.Cross(sliceAxisY, sliceNormalStandardized, sliceAxisX)
+
ShowAngle()
     else:
+
</pre>
        sliceAxisX = backupViewRightDirection
+
 
    # Set slice axes
+
==Set slice position and orientation from 3 markup fiducials==
    sliceNode.SetSliceToRASByNTP(sliceNormalStandardized[0], sliceNormalStandardized[1], sliceNormalStandardized[2],
+
 
        sliceAxisX[0], sliceAxisX[1], sliceAxisX[2],
+
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.
        slicePosition[0], slicePosition[1], slicePosition[2], 0)
 
  
# Example usage:
+
<pre>
sliceNode = getNode('vtkMRMLSliceNodeRed')
+
# Update plane from fiducial points
transformNode = getNode('Transform_3')
+
def UpdateSlicePlane(param1=None, param2=None):
transformMatrix = vtk.vtkMatrix4x4()
+
  # Get point positions as numpy array
transformNode.GetMatrixTransformToParent(transformMatrix)
+
  import numpy as np
sliceNormal = [transformMatrix.GetElement(0,2), transformMatrix.GetElement(1,2), transformMatrix.GetElement(2,2)]
+
  nOfFiduciallPoints = markups.GetNumberOfFiducials()
slicePosition = [transformMatrix.GetElement(0,3), transformMatrix.GetElement(1,3), transformMatrix.GetElement(2,3)]
+
  if nOfFiduciallPoints < 3:
setSlicePoseFromSliceNormalAndPosition(sliceNode, sliceNormal, slicePosition)
+
    return  # not enough points
</pre>
+
  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)
  
== Switching to markup fiducial placement mode ==
+
# Get markup node from scene
 +
sliceNode = slicer.app.layoutManager().sliceWidget('Red').mrmlSliceNode()
 +
markups = slicer.util.getNode('F')
  
To activate a fiducial placement mode, both interaction mode has to be set and a fiducial node has to be selected:
+
# Update slice plane manually
 +
UpdateSlicePlane()
  
<pre>
+
# Update slice plane automatically whenever points are changed
interactionNode = slicer.app.applicationLogic().GetInteractionNode()
+
markupObservation = [markups, markups.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, UpdateSlicePlane, 2)]
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)
 
 
</pre>
 
</pre>
  
Alternatively, ''qSlicerMarkupsPlaceWidget'' widget can be used to initiate markup placement:
+
To stop automatic updates, run this:
 
 
 
<pre>
 
<pre>
# Temporary markups node
+
markupObservation[0].RemoveObserver(markupObservation[1])
markupsNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsFiducialNode")
+
</pre>
  
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.
+
==Set slice position and orientation from a normal vector and position==
placeWidget = slicer.qSlicerMarkupsPlaceWidget()
 
placeWidget.setMRMLScene(slicer.mrmlScene)
 
placeWidget.setCurrentNode(markupsNode)
 
placeWidget.buttonsVisible=False
 
placeWidget.placeButton().show()
 
placeWidget.connect('activeMarkupsFiducialPlaceModeChanged(bool)', placementModeChanged)
 
placeWidget.show()
 
</pre>
 
  
== Change markup fiducial display properties ==
+
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.
 
 
Display properties are stored in display node(s) associated with the fiducial node.
 
  
 
<pre>
 
<pre>
fiducialNode = getNode('F')
+
def setSlicePoseFromSliceNormalAndPosition(sliceNode, sliceNormal, slicePosition, defaultViewUpDirection=None, backupViewRightDirection=None):
fiducialDisplayNode = fiducialNode.GetDisplayNode()
+
    """
fiducialDisplayNode.SetVisibility(False) # Hide all points
+
    Set slice pose from the provided plane normal and position. View up direction is determined automatically,
fiducialDisplayNode.SetVisibility(True) # Show all points
+
    to make view up point towards defaultViewUpDirection.
fiducialDisplayNode.SetSelectedColor(1,1,0) # Set color to yellow
+
    :param defaultViewUpDirection Slice view will be spinned in-plane to match point approximately this up direction. Default: patient superior.
fiducialDisplayNode.SetViewNodeIDs(["vtkMRMLSliceNodeRed", "vtkMRMLViewNode1"]) # Only show in red slice view and first 3D view
+
    :param backupViewRightDirection Slice view will be spinned in-plane to match point approximately this right direction
</pre>
+
        if defaultViewUpDirection is too similar to sliceNormal. Default: patient left.
 
+
    """
== Get a notification if a markup point position is modified ==
+
    # Fix up input directions
 
+
    if defaultViewUpDirection is None:
Event management of Slicer-4.11 version is still subject to change. The example below shows how point manipulation can be observed now.
+
        defaultViewUpDirection = [0,0,1]
 
+
     if backupViewRightDirection is None:
<pre>
+
        backupViewRightDirection = [-1,0,0]
def onMarkupChanged(caller,event):
+
     if sliceNormal[1]>=0:
    markupsNode = caller
+
         sliceNormalStandardized = sliceNormal
     sliceView = markupsNode.GetAttribute('Markups.MovingInSliceView')
+
    else:
    movingMarkupIndex = markupsNode.GetDisplayNode().GetActiveControlPoint()
+
        sliceNormalStandardized = [-sliceNormal[0], -sliceNormal[1], -sliceNormal[2]]
     if movingMarkupIndex >= 0:
+
    # Compute slice axes
         pos = [0,0,0]
+
    sliceNormalViewUpAngle = vtk.vtkMath.AngleBetweenVectors(sliceNormalStandardized, defaultViewUpDirection)
        markupsNode.GetNthFiducialPosition(movingMarkupIndex, pos)
+
    angleTooSmallThresholdRad = 0.25 # about 15 degrees
        isPreview = markupsNode.GetNthControlPointPositionStatus(movingMarkupIndex) == slicer.vtkMRMLMarkupsNode.PositionPreview
+
    if sliceNormalViewUpAngle > angleTooSmallThresholdRad and sliceNormalViewUpAngle < vtk.vtkMath.Pi() - angleTooSmallThresholdRad:
         if isPreview:
+
        viewUpDirection = defaultViewUpDirection
            logging.info("Point {0} is previewed at {1} in slice view {2}".format(movingMarkupIndex, pos, sliceView))
+
        sliceAxisY = viewUpDirection
         else:
+
         sliceAxisX = [0, 0, 0]
            logging.info("Point {0} was moved {1} in slice view {2}".format(movingMarkupIndex, pos, sliceView))
+
         vtk.vtkMath.Cross(sliceAxisY, sliceNormalStandardized, sliceAxisX)
 
     else:
 
     else:
         logging.info("Points modified: slice view = {0}".format(sliceView))
+
         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)
  
def onMarkupStartInteraction(caller, event):
+
# Example usage:
    markupsNode = caller
+
sliceNode = getNode('vtkMRMLSliceNodeRed')
    sliceView = markupsNode.GetAttribute('Markups.MovingInSliceView')
+
transformNode = getNode('Transform_3')
    movingMarkupIndex = markupsNode.GetDisplayNode().GetActiveControlPoint()   
+
transformMatrix = vtk.vtkMatrix4x4()
    logging.info("Start interaction: point ID = {0}, slice view = {1}".format(movingMarkupIndex, sliceView))
+
transformNode.GetMatrixTransformToParent(transformMatrix)
 
+
sliceNormal = [transformMatrix.GetElement(0,2), transformMatrix.GetElement(1,2), transformMatrix.GetElement(2,2)]
def onMarkupEndInteraction(caller, event):
+
slicePosition = [transformMatrix.GetElement(0,3), transformMatrix.GetElement(1,3), transformMatrix.GetElement(2,3)]
    markupsNode = caller
+
setSlicePoseFromSliceNormalAndPosition(sliceNode, sliceNormal, slicePosition)
    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)
 
 
</pre>
 
</pre>
  
== Get a notification if a transform is modified ==
+
==Extract randomly oriented slabs of given shape from a volume==
 
 
<pre>
 
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)
 
</pre>
 
  
== Show a context menu when a markup point is clicked in a slice or 3D view ==
+
Returns a numpy array of sliceCount random tiles.
  
 
<pre>
 
<pre>
 +
def randomSlices(volume, sliceCount, sliceShape):
 +
    layoutManager = slicer.app.layoutManager()
 +
    redWidget = layoutManager.sliceWidget('Red')
 +
    sliceNode = redWidget.mrmlSliceNode()
 +
    sliceNode.SetDimensions(*sliceShape, 1)
 +
    sliceNode.SetFieldOfView(*sliceShape, 1)
 +
    bounds = [0]*6
 +
    volume.GetRASBounds(bounds)
 +
    imageReslice = redWidget.sliceLogic().GetBackgroundLayer().GetReslice()
  
# Example actions to perform
+
    sliceSize = sliceShape[0] * sliceShape[1]
 +
    X = numpy.zeros([sliceCount, sliceSize])
  
def action1():
+
    for sliceIndex in range(sliceCount):
  print('Action1 on markup '+str(slicer.clickedMarkupIndex))
+
        position = numpy.random.rand(3) * 2 - 1
 +
        position = [bounds[0] + bounds[1]-bounds[0] * position[0],
 +
                    bounds[2] + bounds[3]-bounds[2] * position[1],
 +
                    bounds[4] + bounds[5]-bounds[4] * position[2]]
 +
        normal = numpy.random.rand(3) * 2 - 1
 +
        normal = normal / numpy.linalg.norm(normal)
 +
        transverse = numpy.cross(normal, [0,0,1])
 +
        orientation = 0
 +
        sliceNode.SetSliceToRASByNTP( normal[0], normal[1], normal[2],
 +
                                      transverse[0], transverse[1], transverse[2],
 +
                                      position[0], position[1], position[2],
 +
                                      orientation)
 +
        if sliceIndex % 100 == 0:
 +
            slicer.app.processEvents()
 +
        imageReslice.Update()
 +
        imageData = imageReslice.GetOutputDataObject(0)
 +
        array = vtk.util.numpy_support.vtk_to_numpy(imageData.GetPointData().GetScalars())
 +
        X[sliceIndex] = array
 +
    return X
 +
</pre>
  
def action2():
+
==Switching to markup fiducial placement mode==
  print('Action2 on markup '+str(slicer.clickedMarkupIndex))
 
  
def action3():
+
To activate a fiducial placement mode, both interaction mode has to be set and a fiducial node has to be selected:
  print('Action3 on markup '+str(slicer.clickedMarkupIndex))
 
  
# Clicked markup index is saved here to let the action
+
<pre>
# know which markup needs to be manipulated.
+
interactionNode = slicer.app.applicationLogic().GetInteractionNode()
slicer.clickedMarkupIndex = -1
+
selectionNode = slicer.app.applicationLogic().GetSelectionNode()
 
+
selectionNode.SetReferenceActivePlaceNodeClassName("vtkMRMLMarkupsFiducialNode")
# Create a simple menu
+
fiducialNode = slicer.vtkMRMLMarkupsFiducialNode()
 +
slicer.mrmlScene.AddNode(fiducialNode)
 +
fiducialNode.CreateDefaultDisplayNodes()
 +
selectionNode.SetActivePlaceNodeID(fiducialNode.GetID())
 +
interactionNode.SetCurrentInteractionMode(interactionNode.Place)
 +
</pre>
  
menu = qt.QMenu()
+
Alternatively, ''qSlicerMarkupsPlaceWidget'' widget can be used to initiate markup placement:
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
+
<pre>
 +
# Temporary markups node
 +
markupsNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsFiducialNode")
  
@vtk.calldata_type(vtk.VTK_INT)
+
def placementModeChanged(active):
def markupClickedCallback(caller, eventId, callData):
+
   print("Placement: " +("active" if active else "inactive"))
  slicer.clickedMarkupIndex = callData
+
   # You can inspect what is in the markups node here, delete the temporary markup node, etc.
   print('Open menu on markup '+str(slicer.clickedMarkupIndex))
 
   menu.move(qt.QCursor.pos())
 
  menu.show()
 
  
markupsNode = getNode('F')
+
# Create and set up widget that contains a single "place markup" button. The widget can be placed in the module GUI.
observerTag = markupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointClickedEvent, markupClickedCallback)
+
placeWidget = slicer.qSlicerMarkupsPlaceWidget()
 +
placeWidget.setMRMLScene(slicer.mrmlScene)
 +
placeWidget.setCurrentNode(markupsNode)
 +
placeWidget.buttonsVisible=False
 +
placeWidget.placeButton().show()
 +
placeWidget.connect('activeMarkupsFiducialPlaceModeChanged(bool)', placementModeChanged)
 +
placeWidget.show()
 +
</pre>
  
</pre>
+
==Change markup fiducial display properties==
  
== Write markup positions to JSON file ==
+
Display properties are stored in display node(s) associated with the fiducial node.
  
 
<pre>
 
<pre>
markupNode = getNode('F')
+
fiducialNode = getNode('F')
outputFileName = 'c:/tmp/test.json'
+
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
 +
</pre>
  
# Get markup positions
+
==Get a notification if a markup point position is modified==
data = []
 
for fidIndex in range(markupNode.GetNumberOfFiducials()):
 
  coords=[0,0,0]
 
  markupNode.GetNthFiducialPosition(fidIndex,coords)
 
  data.append({'label': markupNode.GetNthFiducialLabel(), 'position': coords})
 
  
import json
+
Event management of Slicer-4.11 version is still subject to change. The example below shows how point manipulation can be observed now.
with open(outputFileName, 'w') as outfile:
 
  json.dump(data, outfile)
 
</pre>
 
 
 
== Write annotation ROI to JSON file ==
 
  
 
<pre>
 
<pre>
roiNode = getNode('R')
+
def onMarkupChanged(caller,event):
outputFileName = "c:/tmp/test.json"
+
    markupsNode = caller
 
+
    sliceView = markupsNode.GetAttribute('Markups.MovingInSliceView')
# Get annotation ROI data
+
    movingMarkupIndex = markupsNode.GetDisplayNode().GetActiveControlPoint()
center = [0,0,0]
+
    if movingMarkupIndex >= 0:
radius = [0,0,0]
+
        pos = [0,0,0]
roiNode.GetControlPointWorldCoordinates(0, center)
+
        markupsNode.GetNthFiducialPosition(movingMarkupIndex, pos)
roiNode.GetControlPointWorldCoordinates(1, radius)
+
        isPreview = markupsNode.GetNthControlPointPositionStatus(movingMarkupIndex) == slicer.vtkMRMLMarkupsNode.PositionPreview
data = {'center': radius, 'radius': radius}
+
        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))
  
# Write to json file
+
markupsNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsFiducialNode")
import json
+
markupsNode.CreateDefaultDisplayNodes()
with open(outputFileName, 'w') as outfile:
+
markupsNode.AddFiducial(0,0,0)
  json.dump(data, outfile)
+
markupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, onMarkupChanged)
 +
markupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointStartInteractionEvent, onMarkupStartInteraction)
 +
markupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointEndInteractionEvent, onMarkupEndInteraction)
 
</pre>
 
</pre>
  
== Show a simple surface mesh as a model node ==
+
==Get a notification if a transform is modified==
 
 
This example shows how to display a simple surface mesh (a box, created by a VTK source filter) as a model node.
 
  
 
<pre>
 
<pre>
# Create and set up polydata source
+
def onTransformNodeModified(transformNode, unusedArg2=None, unusedArg3=None):
box = vtk.vtkCubeSource()
+
  transformMatrix = vtk.vtkMatrix4x4()
box.SetXLength(30)
+
  transformNode.GetMatrixTransformToWorld(transformMatrix)
box.SetYLength(20)
+
  print("Position: [{0}, {1}, {2}]".format(transformMatrix.GetElement(0,3), transformMatrix.GetElement(1,3), transformMatrix.GetElement(2,3)))
box.SetZLength(15)
 
box.SetCenter(10,20,5)
 
  
# Create a model node that displays output of the source
+
transformNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLTransformNode")
boxNode = slicer.modules.models.logic().AddModel(box.GetOutputPort())
+
transformNode.AddObserver(slicer.vtkMRMLTransformNode.TransformModifiedEvent, onTransformNodeModified)
 +
</pre>
  
# Adjust display properties
+
==Rotate a node around a specified point==
boxNode.GetDisplayNode().SetColor(1,0,0)
 
boxNode.GetDisplayNode().SetOpacity(0.8)
 
</pre>
 
  
== Add a texture mapped plane to the scene as a model ==
+
Set up the scene:
Note that model textures are not exposed in the GUI and are not saved in the scene
 
<pre>
 
# use dummy image data here
 
e = vtk.vtkImageEllipsoidSource()
 
  
scene = slicer.mrmlScene
+
*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.
  
# Create model node
+
Then run the script below, go to Transforms module, select rotationTransformNode, and move rotation sliders.
model = slicer.vtkMRMLModelNode()
 
model.SetScene(scene)
 
model.SetName(scene.GenerateUniqueName("2DImageModel"))
 
  
planeSource = vtk.vtkPlaneSource()
+
<pre>
model.SetAndObservePolyData(planeSource.GetOutput())
+
# 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')
  
# Create display node
+
def updateFinalTransform(unusedArg1=None, unusedArg2=None, unusedArg3=None):
modelDisplay = slicer.vtkMRMLModelDisplayNode()
+
    rotationMatrix = vtk.vtkMatrix4x4()
modelDisplay.SetColor(1,1,0) # yellow
+
    rotationTransformNode.GetMatrixTransformToParent(rotationMatrix)
modelDisplay.SetBackfaceCulling(0)
+
    rotationCenterPointCoord = [0.0, 0.0, 0.0]
modelDisplay.SetScene(scene)
+
    centerOfRotationMarkupsNode.GetNthControlPointPositionWorld(0, rotationCenterPointCoord)
scene.AddNode(modelDisplay)
+
    finalTransform = vtk.vtkTransform()
model.SetAndObserveDisplayNodeID(modelDisplay.GetID())
+
    finalTransform.Translate(rotationCenterPointCoord)
 +
    finalTransform.Concatenate(rotationMatrix)
 +
    finalTransform.Translate(-rotationCenterPointCoord[0], -rotationCenterPointCoord[1], -rotationCenterPointCoord[2])
 +
    finalTransformNode.SetAndObserveMatrixTransformToParent(finalTransform.GetMatrix())
  
# Add to scene
+
# Manual initial update
modelDisplay.SetAndObserveTextureImageData(e.GetOutput())
+
updateFinalTransform()
scene.AddNode(model)  
 
  
 +
# Automatic update when point is moved or transform is modified
 +
rotationTransformNodeObserver = rotationTransformNode.AddObserver(slicer.vtkMRMLTransformNode.TransformModifiedEvent, updateFinalTransform)
 +
centerOfRotationMarkupsNodeObserver = centerOfRotationMarkupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, updateFinalTransform)
  
transform = slicer.vtkMRMLLinearTransformNode()
+
# Execute these lines to stop automatic updates:
scene.AddNode(transform)  
+
# rotationTransformNode.RemoveObserver(rotationTransformNodeObserver)
model.SetAndObserveTransformNodeID(transform.GetID())
+
# centerOfRotationMarkupsNode.RemoveObserver(centerOfRotationMarkupsNodeObserver)
  
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 ==
+
==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.
  
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.
+
Then run the script below, go to Transforms module, select rotationTransformNode, and move Edit / Rotation / IS slider.
  
 
<pre>
 
<pre>
modelNode = getNode('sphere')
+
# This markups fiducial node specifies the center of rotation
modelPointValues = modelNode.GetPolyData().GetPointData().GetArray("Normals")
+
rotationAxisMarkupsNode = getNode('L')
markupsNode = getNode('F')
+
# This transform can be edited in Transforms module (Edit / Rotation / IS slider)
 
+
rotationTransformNode = getNode('LinearTransform_3')
if not markupsNode:
+
# This transform has to be applied to the image, model, etc.
  markupsNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsFiducialNode","F")
+
finalTransformNode = getNode('LinearTransform_4')
 
+
 
pointsLocator = vtk.vtkPointLocator() # could try using vtk.vtkStaticPointLocator() if need to optimize
+
def updateFinalTransform(unusedArg1=None, unusedArg2=None, unusedArg3=None):
pointsLocator.SetDataSet(modelNode.GetPolyData())
+
    import numpy as np
pointsLocator.BuildLocator()
+
    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 aligned 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())
  
def onMouseMoved(observer,eventid): 
+
# Manual initial update
  ras=[0,0,0]
+
updateFinalTransform()
  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')  
+
# Automatic update when point is moved or transform is modified
observationId = crosshairNode.AddObserver(slicer.vtkMRMLCrosshairNode.CursorPositionModifiedEvent, onMouseMoved)
+
rotationTransformNodeObserver = rotationTransformNode.AddObserver(slicer.vtkMRMLTransformNode.TransformModifiedEvent, updateFinalTransform)
 +
rotationAxisMarkupsNodeObserver = rotationAxisMarkupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, updateFinalTransform)
  
# To stop printing of values run this:
+
# Execute these lines to stop automatic updates:
# crosshairNode.RemoveObserver(observationId)
+
# rotationTransformNode.RemoveObserver(rotationTransformNodeObserver)
 +
# rotationAxisMarkupsNode.RemoveObserver(rotationAxisMarkupsNodeObserver)
 
</pre>
 
</pre>
  
== Select cells of a using markups fiducial points ==
+
==Show a context menu when a markup point is clicked in a slice or 3D view==
  
The following script selects cells of a model node that are closest to positions of markups fiducial points.
+
Subject hierarchy plugins can offer actions in the view context menu when right-clicking objects that support such picking (such as Markups fiducials). A comprehensive [https://github.com/Slicer/Slicer/blob/master/Modules/Loadable/Annotations/SubjectHierarchyPlugins/AnnotationsSubjectHierarchyPlugin.py subject hierarchy plugin example] is for the Annotations module.
  
 
<pre>
 
<pre>
# 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
+
  def viewContextMenuActions(self):
selectionArray = vtk.vtkIntArray()
+
    return [self.doSomething]
selectionArray.SetName('selection')
+
 
selectionArray.SetNumberOfValues(modelNode.GetMesh().GetNumberOfCells())
+
  def showViewContextMenuActionsForItem(self, itemID, eventData):
selectionArray.Fill(0)
+
    if not itemID:
 +
      logging.error('Invalid item for view context menu ' + str(itemID))
 +
      return
  
cellScalars = modelPointValues = modelNode.GetMesh().GetCellData()
+
    pluginHandlerSingleton = slicer.qSlicerSubjectHierarchyPluginHandler.instance()
cellScalars.AddArray(selectionArray)
+
    shNode = pluginHandlerSingleton.subjectHierarchyNode()
 +
    if shNode is None:
 +
      logging.error('Failed to access subject hierarchy node')
 +
      return
  
modelNode.GetDisplayNode().SetActiveScalar("selection", vtk.vtkAssignAttribute.CELL_DATA)
+
    associatedNode = shNode.GetItemDataNode(itemID)
modelNode.GetDisplayNode().SetScalarVisibility(True)
+
    if not associatedNode or not associatedNode.IsA("vtkMRMLMarkupsNode"):
 +
      return
  
cell = vtk.vtkCellLocator()
+
    self.viewMenuEventData = eventData
cell.SetDataSet(modelNode.GetMesh())
+
    self.viewMenuEventData['NodeID'] = associatedNode.GetID()
cell.BuildLocator()
 
  
def onPointsModified(observer=None, eventid=None):
+
  def onDoSomething(self):
     global markupsNode, selectionArray
+
     nodeID = self.viewMenuEventData['NodeID']
    selectionArray.Fill(0) # set all cells to non-selected by default
+
     markupsNode = slicer.mrmlScene.GetNodeByID(nodeID)
     markupPoints = slicer.util.arrayFromMarkupsControlPoints(markupsNode)
+
     if markupsNode is None or not markupsNode.IsA("vtkMRMLMarkupsNode"):
     for markupPoint in markupPoints:
+
      logging.error('Failed to get fiducial markups node by ID ' + str(nodeID))
        closestCell = cell.FindCell(markupPoint)
+
      return
        if closestCell >=0:
 
            selectionArray.SetValue(closestCell, 100) # set selected cell's scalar value to non-zero
 
    selectionArray.Modified()
 
  
# Initial update
+
    componentIndex = self.viewMenuEventData['ComponentIndex']
onPointsModified()
+
    markupID = markupsNode.GetNthMarkupID(componentIndex)
# Automatic update each time when a markup point is modified
+
   
markupsNodeObserverTag = markupsNode.AddObserver(slicer.vtkMRMLMarkupsFiducialNode.PointModifiedEvent, onPointsModified)
+
    # Do something with the clicked fiducial
  
# To stop updating selection, run this:
 
# markupsNode.RemoveObserver(markupsNodeObserverTag)
 
 
</pre>
 
</pre>
  
== Export entire scene as VRML ==
+
==Write markup positions to JSON file==
 
 
Save all surface meshes displayed in the scene (models, markups, etc). Solid colors and coloring by scalar is preserved. Textures are not supported.
 
  
 
<pre>
 
<pre>
exporter = vtk.vtkVRMLExporter()
+
markupNode = getNode('F')
exporter.SetRenderWindow(slicer.app.layoutManager().threeDWidget(0).threeDView().renderWindow())
+
outputFileName = 'c:/tmp/test.json'
exporter.SetFileName('C:/tmp/something.wrl')
 
exporter.Write()
 
</pre>
 
  
== Export model to Blender, including color by scalar ==
+
# 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})
  
<pre>
+
import json
modelNode = getNode("Model")
+
with open(outputFileName, 'w') as outfile:
plyFilePath = "c:/tmp/model.ply"
+
  json.dump(data, outfile)
 
 
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()
 
 
</pre>
 
</pre>
  
== Export a tract (FiberBundle) to Blender, including color ==
+
==Write annotation ROI to JSON file==
<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:
 
  
 
<pre>
 
<pre>
lineDisplayNode = getNode("*LineDisplay*")
+
roiNode = getNode('R')
plyFilePath = "/tmp/fibers.ply"
+
outputFileName = "c:/tmp/test.json"
  
tuber = vtk.vtkTubeFilter()
+
# Get annotation ROI data
tuber.SetInputData(lineDisplayNode.GetOutputPolyData())
+
center = [0,0,0]
tuber.Update()
+
radius = [0,0,0]
tubes = tuber.GetOutputDataObject(0)
+
roiNode.GetControlPointWorldCoordinates(0, center)
scalars = tubes.GetPointData().GetArray(0)
+
roiNode.GetControlPointWorldCoordinates(1, radius)
scalars.SetName("scalars")
+
data = {'center': radius, 'radius': radius}
  
triangles = vtk.vtkTriangleFilter()
+
# Write to json file
triangles.SetInputData(tubes)
+
import json
triangles.Update()
+
with open(outputFileName, 'w') as outfile:
 
+
  json.dump(data, outfile)
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()
 
 
</pre>
 
</pre>
  
== Iterate over tract (FiberBundle) streamline points ==
+
==Show a simple surface mesh as a model node==
  
This example shows how to access the points in each line of a FiberBundle as a numpy array (view).
+
This example shows how to display a simple surface mesh (a box, created by a VTK source filter) as a model node.
  
 
<pre>
 
<pre>
from vtk.util.numpy_support import vtk_to_numpy
+
# Create and set up polydata source
 +
box = vtk.vtkCubeSource()
 +
box.SetXLength(30)
 +
box.SetYLength(20)
 +
box.SetZLength(15)
 +
box.SetCenter(10,20,5)
  
fb = getNode("FiberBundle_F") # <- fill in node ID here
+
# Create a model node that displays output of the source
 +
boxNode = slicer.modules.models.logic().AddModel(box.GetOutputPort())
  
# get point data as 1d array
+
# Adjust display properties
points = slicer.util.arrayFromModelPoints(fb)
+
boxNode.GetDisplayNode().SetColor(1,0,0)
 +
boxNode.GetDisplayNode().SetOpacity(0.8)
 +
</pre>
  
# get line cell ids as 1d array
+
==Measure distance of points from surface==
line_ids = vtk_to_numpy(fb.GetPolyData().GetLines().GetData())
 
  
# VTK cell ids are stored as
+
This example computes closest distance of points (markups fiducial 'F') from a surface (model node 'mymodel') and writes results into a table.
#  [ 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`
+
<pre>
     index_array = line_ids[ cur_idx : cur_idx + count]
+
markupsNode = getNode('F')
     # update to the next range
+
modelNode = getNode('mymodel')
     cur_idx += count + 1
+
 
 +
# 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()
  
    # - index the point array by those ids
+
# Create arrays to store results
    line_points = points[index_array]
+
indexCol = vtk.vtkIntArray()
 +
indexCol.SetName("Index")
 +
labelCol = vtk.vtkStringArray()
 +
labelCol.SetName("Name")
 +
distanceCol = vtk.vtkDoubleArray()
 +
distanceCol.SetName("Distance")
  
     # do work here
+
distanceFilter = vtk.vtkImplicitPolyDataDistance()
</pre>
+
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)
  
== Clone a node ==
+
# Create a table from result arrays
 +
resultTableNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLTableNode", "Points from surface distance")
 +
resultTableNode.AddColumn(indexCol)
 +
resultTableNode.AddColumn(labelCol)
 +
resultTableNode.AddColumn(distanceCol)
  
This example shows how to make a copy of any node that appears in Subject Hierarchy (in Data module).
+
# Show table in view layout
 +
slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutFourUpTableView)
 +
slicer.app.applicationLogic().GetSelectionNode().SetReferenceActiveTableID(resultTableNode.GetID())
 +
slicer.app.applicationLogic().PropagateTableSelection()
 +
</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>
# Get a node from SampleData that we will clone
+
# Create model node
import SampleData
+
planeSource = vtk.vtkPlaneSource()
nodeToClone = SampleData.SampleDataLogic().downloadMRHead()
+
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)
  
# Clone the node
+
# Add texture (just use image of an ellipsoid)
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)
+
e = vtk.vtkImageEllipsoidSource()
itemIDToClone = shNode.GetItemByDataNode(nodeToClone)
+
modelDisplay.SetTextureImageDataConnection(e.GetOutputPort())
clonedItemID = slicer.modules.subjecthierarchy.logic().CloneSubjectHierarchyItem(shNode, itemIDToClone)
 
clonedNode = shNode.GetItemDataNode(clonedItemID)
 
 
</pre>
 
</pre>
  
== Clone a volume ==
+
==Get scalar values at surface of a model==
This example shows how to clone the MRHead sample volume, including its pixel data and display settings.
+
 
 +
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>
sourceVolumeNode = slicer.util.getNode('MRHead')
+
modelNode = getNode('sphere')
volumesLogic = slicer.modules.volumes.logic()
+
modelPointValues = modelNode.GetPolyData().GetPointData().GetArray("Normals")
clonedVolumeNode = volumesLogic.CloneVolume(slicer.mrmlScene, sourceVolumeNode, 'Cloned volume')
+
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)
 
</pre>
 
</pre>
  
== Create a new volume ==
+
==Apply VTK filter on a model node==
This example shows how to create a new empty volume.
+
 
 
<pre>
 
<pre>
nodeName = "MyNewVolume"
+
modelNode = getNode('tip')
imageSize = [512, 512, 512]
+
 
voxelType=vtk.VTK_UNSIGNED_CHAR
+
# Compute curvature
imageOrigin = [0.0, 0.0, 0.0]
+
curv = vtk.vtkCurvatures()
imageSpacing = [1.0, 1.0, 1.0]
+
curv.SetInputData(modelNode.GetPolyData())
imageDirections = [[1,0,0], [0,1,0], [0,0,1]]
+
modelNode.SetPolyDataConnection(curv.GetOutputPort())
fillVoxelValue = 0
 
  
# Create an empty image volume, filled with fillVoxelValue
+
# Set up coloring by Curvature
imageData = vtk.vtkImageData()
+
modelNode.GetDisplayNode().SetActiveScalar("Gauss_Curvature", vtk.vtkAssignAttribute.POINT_DATA)
imageData.SetDimensions(imageSize)
+
modelNode.GetDisplayNode().SetAndObserveColorNodeID("Viridis")
imageData.AllocateScalars(voxelType, 1)
+
modelNode.GetDisplayNode().SetScalarVisibility(True)
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 ==
+
==Select cells of a model using markups fiducial points==
  
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 following script selects cells of a model node that are closest to positions of markups fiducial points.
  
 
<pre>
 
<pre>
nodeName = 'MRHead'
+
# Get input nodes
thresholdValue = 100
+
modelNode = slicer.util.getNode('Segment_1') # select cells in this model
voxelArray = array(nodeName) # get voxels as numpy array
+
markupsNode = slicer.util.getNode('F') # points will be selected at positions specified by this markups fiducial node
voxelArray[voxelArray < thresholdValue] = 0 # modify voxel values
+
 
getNode(nodeName).Modified() # at the end of all processing, notify Slicer that the image modification is completed
+
# Create scalar array that will store selection state
</pre>
+
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)
  
This example shows how to change voxels values of the MRHead sample volume.
+
# Set up coloring by selection array
The values will be computed by function f(r,a,s,) = (r-10)*(r-10)+(a+15)*(a+15)+s*s.
+
modelNode.GetDisplayNode().SetActiveScalar("selection", vtk.vtkAssignAttribute.CELL_DATA)
<pre>
+
modelNode.GetDisplayNode().SetAndObserveColorNodeID("vtkMRMLColorTableNodeWarm1")
volumeNode=slicer.util.getNode('MRHead')
+
modelNode.GetDisplayNode().SetScalarVisibility(True)
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.SetScalarComponentFromFloat(distortionVectorPosition_Ijk[0], distortionVectorPosition_Ijk[1], distortionVectorPosition_Ijk[2], 0, fillValue)
 
imageData.Modified()
 
</pre>
 
  
== Get volume voxel coordinates from markup fiducial RAS coordinates ==
+
# Initialize cell locator
 +
cell = vtk.vtkCellLocator()
 +
cell.SetDataSet(modelNode.GetMesh())
 +
cell.BuildLocator()
  
This example shows how to get voxel coordinate of a volume corresponding to a markup fiducial point position.
+
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()
  
<pre>
+
# Initial update
# Inputs
+
onPointsModified()
volumeNode = getNode('MRHead')
+
# Automatic update each time when a markup point is modified
markupsNode = getNode('F')
+
markupsNodeObserverTag = markupsNode.AddObserver(slicer.vtkMRMLMarkupsFiducialNode.PointModifiedEvent, onPointsModified)
markupsIndex = 0
 
  
# Get point coordinate in RAS
+
# To stop updating selection, run this:
point_Ras = [0, 0, 0, 1]
+
# markupsNode.RemoveObserver(markupsNodeObserverTag)
markupsNode.GetNthFiducialWorldCoordinates(markupsIndex, point_Ras)
+
</pre>
  
# If volume node is transformed, apply that transform to get volume's RAS coordinates
+
==Load volume from .vti file==
transformRasToVolumeRas = vtk.vtkGeneralTransform()
 
slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(None, volumeNode.GetParentTransformNode(), transformRasToVolumeRas)
 
point_VolumeRas = transformRasToVolumeRas.TransformPoint(point_Ras[0:3])
 
  
# Get voxel coordinates from physical coordinates
+
Slicer does not provide reader for VTK XML image data file format (as they are not commonly used for storing medical images and they cannot store image axis directions) but such files can be read by using this script:
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
+
<pre>
print(point_Ijk)
+
reader=vtk.vtkXMLImageDataReader()
 +
reader.SetFileName("/path/to/file.vti")
 +
reader.Update()
 +
imageData = reader.GetOutput()
 +
spacing = imageData.GetSpacing()
 +
origin = imageData.GetOrigin()
 +
imageData.SetOrigin(0,0,0)
 +
imageData.SetSpacing(1,1,1)
 +
volumeNode=slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode")
 +
volumeNode.SetAndObserveImageData(imageData)
 +
volumeNode.SetSpacing(spacing)
 +
volumeNode.SetOrigin(origin)
 +
slicer.util.setSliceViewerLayers(volumeNode, fit=True)
 
</pre>
 
</pre>
  
== Get markup fiducial RAS coordinates from volume voxel coordinates ==
+
==Export entire scene as VRML==
  
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.
+
Save all surface meshes displayed in the scene (models, markups, etc). Solid colors and coloring by scalar is preserved. Textures are not supported.
  
 
<pre>
 
<pre>
# Inputs
+
exporter = vtk.vtkVRMLExporter()
volumeNode = getNode('MRHead')
+
exporter.SetRenderWindow(slicer.app.layoutManager().threeDWidget(0).threeDView().renderWindow())
markupsNode = getNode('F')
+
exporter.SetFileName('C:/tmp/something.wrl')
 
+
exporter.Write()
# 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)
 
 
</pre>
 
</pre>
  
== Get the values of all voxels for a label value  ==
+
==Export model to Blender, including color by scalar==
 
 
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>
 +
modelNode = getNode("Model")
 +
plyFilePath = "c:/tmp/model.ply"
  
import numpy
+
modelDisplayNode = modelNode.GetDisplayNode()
volume = array(‘Volume’)
+
triangles = vtk.vtkTriangleFilter()
label = array(‘Volume-label’)
+
triangles.SetInputConnection(modelDisplayNode.GetOutputPolyDataConnection())
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)
 
</pre>
 
  
== Access values in a DTI tensor volume ==
+
plyWriter = vtk.vtkPLYWriter()
This example shows how to access individual tensors at the voxel level.
+
plyWriter.SetInputConnection(triangles.GetOutputPort())
 +
lut = vtk.vtkLookupTable()
 +
lut.DeepCopy(modelDisplayNode.GetColorNode().GetLookupTable())
 +
lut.SetRange(modelDisplayNode.GetScalarRange())
 +
plyWriter.SetLookupTable(lut)
 +
plyWriter.SetArrayName(modelDisplayNode.GetActiveScalarName())
  
First load your DWI volume and estimate tensors to produce a DTI volume called ‘Output DTI Volume’
+
plyWriter.SetFileName(plyFilePath)
 +
plyWriter.Write()
 +
</pre>
  
Then open the python window: View->Python interactor
+
==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)''.
  
Use this command to access tensors through numpy:
+
The example below shows how to export a tractography "FiberBundleNode" to a PLY file:
  
 
<pre>
 
<pre>
tensors = array('Output DTI Volume')
+
lineDisplayNode = getNode("*LineDisplay*")
</pre>
+
plyFilePath = "/tmp/fibers.ply"
  
Type the following code into the Python window to access all tensor components using vtk commands:
+
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")
  
<pre>
+
plyWriter.SetFileName(plyFilePath)
volumeNode=slicer.util.getNode('Output DTI Volume')
+
plyWriter.Write()
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
 
 
</pre>
 
</pre>
  
== Change window/level (brightness/contrast) or colormap of a volume ==
+
==Iterate over tract (FiberBundle) streamline points==
This example shows how to change window/level of the MRHead sample volume.
+
 
<pre>
+
This example shows how to access the points in each line of a FiberBundle as a numpy array (view).
volumeNode = getNode('MRHead')
 
displayNode = volumeNode.GetDisplayNode()
 
displayNode.AutoWindowLevelOff()
 
displayNode.SetWindow(50)
 
displayNode.SetLevel(100)
 
</pre>
 
  
Change color mapping from grayscale to rainbow:
 
 
<pre>
 
<pre>
displayNode.SetAndObserveColorNodeID('vtkMRMLColorTableNodeRainbow')
+
from vtk.util.numpy_support import vtk_to_numpy
</pre>
 
  
== Make mouse left-click and drag on the image adjust window/level ==
+
fb = getNode("FiberBundle_F") # <- fill in node ID here
  
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:
+
# get point data as 1d array
 +
points = slicer.util.arrayFromModelPoints(fb)
  
<pre>
+
# get line cell ids as 1d array
slicer.app.applicationLogic().GetInteractionNode().SetCurrentInteractionMode(slicer.vtkMRMLInteractionNode.AdjustWindowLevel)
+
line_ids = vtk_to_numpy(fb.GetPolyData().GetLines().GetData())
</pre>
 
  
== Create custom color table ==
+
# VTK cell ids are stored as
This example shows how to create a new color table, for example with inverted color range from the default Ocean color table.
+
#  [ N0 c0_id0 ... c0_id0
<pre>
+
#    N1 c1_id0 ... c1_idN1 ]
invertedocean = slicer.vtkMRMLColorTableNode()
+
# so we need to
invertedocean.SetTypeToUser()
+
# - read point count for each line (cell)
invertedocean.SetNumberOfColors(256)
+
# - grab the ids in that range from `line_ids` array defined above
invertedocean.SetName("InvertedOcean")
+
# - 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
  
for i in range(0,255):
+
    # - index the point array by those ids
     invertedocean.SetColor(i, 0.0, 1 - (i+1e-16)/255.0, 1.0, 1.0)
+
     line_points = points[index_array]
  
slicer.mrmlScene.AddNode(invertedocean)
+
    # do work here
 
</pre>
 
</pre>
  
== Manipulate a Slice View ==
+
==Clone a node==
  
=== Change slice offset ===
+
This example shows how to make a copy of any node that appears in Subject Hierarchy (in Data module).
 
 
Equivalent to moving the slider in slice view controller.
 
  
 
<pre>
 
<pre>
layoutManager = slicer.app.layoutManager()
+
# Get a node from SampleData that we will clone
red = layoutManager.sliceWidget('Red')
+
import SampleData
redLogic = red.sliceLogic()
+
nodeToClone = SampleData.SampleDataLogic().downloadMRHead()
# Print current slice offset position
 
print(redLogic.GetSliceOffset())
 
# Change slice position
 
redLogic.SetSliceOffset(20)
 
</pre>
 
  
=== Change slice orientation ===
+
# Clone the node
 
+
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)
Get 'Red' slice node and rotate around X and Y axes.
+
itemIDToClone = shNode.GetItemByDataNode(nodeToClone)
 
+
clonedItemID = slicer.modules.subjecthierarchy.logic().CloneSubjectHierarchyItem(shNode, itemIDToClone)
<pre>
+
clonedNode = shNode.GetItemDataNode(clonedItemID)
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()
 
 
</pre>
 
</pre>
  
=== Show slice views in 3D window ===
+
==Clone a volume==
 
+
This example shows how to clone the MRHead sample volume, including its pixel data and display settings.
Equivalent to clicking 'eye' icon in the slice view controller.
 
 
 
 
<pre>
 
<pre>
layoutManager = slicer.app.layoutManager()
+
sourceVolumeNode = slicer.util.getNode('MRHead')
for sliceViewName in layoutManager.sliceViewNames():
+
volumesLogic = slicer.modules.volumes.logic()
  controller = layoutManager.sliceWidget(sliceViewName).sliceController()
+
clonedVolumeNode = volumesLogic.CloneVolume(slicer.mrmlScene, sourceVolumeNode, 'Cloned volume')
  controller.setSliceVisible(True)
 
 
</pre>
 
</pre>
  
=== Reset field of view to show background volume maximized ===
+
==Create a new volume==
 
+
This example shows how to create a new empty volume.
Equivalent to click small rectangle button ("Adjust the slice viewer's field of view...") in the slice view controller.
 
 
 
 
<pre>
 
<pre>
slicer.util.resetSliceViews()
+
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()
 
</pre>
 
</pre>
  
=== Rotate slice views to volume plane ===
+
==Get value of a volume at specific voxel coordinates==
  
Aligns slice views to volume axes, shows original image acquisition planes in slice views.
+
This example shows how to get voxel value of "volumeNode" at "ijk" volume voxel coordinates.
  
 
<pre>
 
<pre>
 
volumeNode = slicer.util.getNode('MRHead')
 
volumeNode = slicer.util.getNode('MRHead')
layoutManager = slicer.app.layoutManager()
+
ijk = [20,40,30]  # volume voxel coordinates
for sliceViewName in layoutManager.sliceViewNames():
+
 
  layoutManager.sliceWidget(sliceViewName).mrmlSliceNode().RotateToVolumePlane(volumeNode)
+
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>
 
</pre>
  
=== Iterate over current visible slice views, and set foreground and background images ===
+
==Modify voxels in a volume==
  
<pre>
+
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:
slicer.util.setSliceViewerLayers(background=mrVolume, foreground=ctVolume)
 
</pre>
 
 
 
Internally, this method performs something like this:
 
  
 
<pre>
 
<pre>
for sliceViewName in layoutManager.sliceViewNames():
+
nodeName = 'MRHead'
    sliceWidget = layoutManager.sliceWidget(sliceViewName)
+
thresholdValue = 100
    # setup background volume
+
voxelArray = array(nodeName) # get voxels as numpy array
    compositeNode.SetBackgroundVolumeID(mrVolume.GetID())
+
voxelArray[voxelArray < thresholdValue] = 0 # modify voxel values
    # setup foreground volume
+
getNode(nodeName).Modified() # at the end of all processing, notify Slicer that the image modification is completed
    compositeNode.SetForegroundVolumeID(ctVolume.GetID())
 
    # change opacity
 
    compositeNode.SetForegroundOpacity(0.3)
 
 
</pre>
 
</pre>
  
== Synchronize zoom factor between slice views ==
+
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>
slicer.sliceNodes = [slicer.app.layoutManager().sliceWidget(viewName).mrmlSliceNode()
+
volumeNode=slicer.util.getNode('MRHead')
     for viewName in slicer.app.layoutManager().sliceViewNames()]
+
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()
 +
</pre>
  
slicer.updatingSliceNodes = False
+
==Get volume voxel coordinates from markup fiducial RAS coordinates==
  
def sliceModified(caller, event):
+
This example shows how to get voxel coordinate of a volume corresponding to a markup fiducial point position.
    if slicer.updatingSliceNodes:
 
        # 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:
+
<pre>
    sliceNode.AddObserver(vtk.vtkCommand.ModifiedEvent, sliceModified)
+
# Inputs
</pre>
+
volumeNode = getNode('MRHead')
 +
markupsNode = getNode('F')
 +
markupsIndex = 0
 +
 
 +
# Get point coordinate in RAS
 +
point_Ras = [0, 0, 0, 1]
 +
markupsNode.GetNthFiducialWorldCoordinates(markupsIndex, point_Ras)
  
== Show a volume in slice views ==
+
# 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])
  
Recommended:
+
# 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] ]
  
<pre>
+
# Print output
volumeNode = slicer.util.getNode('YourVolumeNode')
+
print(point_Ijk)
slicer.util.setSliceViewerLayers(background=volumeNode)
 
 
</pre>
 
</pre>
  
or
+
==Get markup fiducial RAS coordinates from volume voxel coordinates==
  
Show volume in all visible views where volume selection propagation is enabled:
+
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.
  
 
<pre>
 
<pre>
volumeNode = slicer.util.getNode('YourVolumeNode')
+
# Inputs
applicationLogic = slicer.app.applicationLogic()
+
volumeNode = getNode('MRHead')
selectionNode = applicationLogic.GetSelectionNode()
+
markupsNode = getNode('F')
selectionNode.SetSecondaryVolumeID(volumeNode.GetID())
+
 
applicationLogic.PropagateForegroundVolumeSelection(0)
+
# Get voxel position in IJK coordinate system
</pre>
+
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]]
  
or
+
# 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)
  
Show volume in selected views:
+
# 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])
  
<pre>
+
# Add a markup at the computed position and print its coordinates
n =  slicer.util.getNode('YourVolumeNode')
+
markupsNode.AddFiducial(point_Ras[0], point_Ras[1], point_Ras[2], "max")
for color in ['Red', 'Yellow', 'Green']:
+
print(point_Ras)
    slicer.app.layoutManager().sliceWidget(color).sliceLogic().GetSliceCompositeNode().SetForegroundVolumeID(n.GetID())
 
 
</pre>
 
</pre>
  
== Change opacity of foreground volume in slice views ==
+
==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:
  
 
<pre>
 
<pre>
slicer.util.setSliceViewerLayers(foregroundOpacity=0.4)
+
 
 +
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)
 
</pre>
 
</pre>
  
or
+
==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
  
Change opacity in a selected view
+
Use this command to access tensors through numpy:
  
 
<pre>
 
<pre>
lm = slicer.app.layoutManager()
+
tensors = array('Output DTI Volume')
sliceLogic = lm.sliceWidget('Red').sliceLogic()
 
compositeNode = sliceLogic.GetSliceCompositeNode()
 
compositeNode.SetForegroundOpacity(0.4)
 
 
</pre>
 
</pre>
  
== Fit slice plane to markup fiducials ==
+
Type the following code into the Python window to access all tensor components using vtk commands:
  
 
<pre>
 
<pre>
sliceNode = slicer.mrmlScene.GetNodeByID("vtkMRMLSliceNodeRed")
+
volumeNode=slicer.util.getNode('Output DTI Volume')
markupsNode = slicer.mrmlScene.GetFirstNodeByName("F")
+
imageData=volumeNode.GetImageData()
# Get markup point positions as numpy arrays
+
tensors = imageData.GetPointData().GetTensors()
import numpy as np
+
extent = imageData.GetExtent()
p1 = np.array([0,0,0])
+
idx = 0
p2 = np.array([0,0,0])
+
for k in range(extent[4], extent[5]+1):
p3 = np.array([0,0,0])
+
  for j in range(extent[2], extent[3]+1):
markupsNode.GetNthFiducialPosition(0, p1)
+
    for i in range(extent[0], extent[1]+1):
markupsNode.GetNthFiducialPosition(1, p2)
+
      tensors.GetTuple9(idx)
markupsNode.GetNthFiducialPosition(2, p3)
+
      idx += 1
# Get plane axis directions
+
</pre>
n = np.cross(p2-p1, p2-p3) # plane normal direction
+
 
n = n/np.linalg.norm(n)
+
==Change window/level (brightness/contrast) or colormap of a volume==
t = np.cross([0, 0, 1], n) # plane transverse direction
+
This example shows how to change window/level of the MRHead sample volume.
t = t/np.linalg.norm(t)
+
<pre>
# Set slice plane orientation and position
+
volumeNode = getNode('MRHead')
sliceNode.SetSliceToRASByNTP(n[0], n[1], n[2], t[0], t[1], t[2], p1[0], p1[1], p1[2], 0)
+
displayNode = volumeNode.GetDisplayNode()
 +
displayNode.AutoWindowLevelOff()
 +
displayNode.SetWindow(50)
 +
displayNode.SetLevel(100)
 +
</pre>
 +
 
 +
Change color mapping from grayscale to rainbow:
 +
<pre>
 +
displayNode.SetAndObserveColorNodeID('vtkMRMLColorTableNodeRainbow')
 
</pre>
 
</pre>
  
== Save a series of images from a Slice View ==
+
==Make mouse left-click and drag on the image adjust window/level==
  
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')"
+
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>
layoutName = 'Green'
+
slicer.app.applicationLogic().GetInteractionNode().SetCurrentInteractionMode(slicer.vtkMRMLInteractionNode.AdjustWindowLevel)
imagePathPattern = '/tmp/image-%03d.png'
+
</pre>
steps = 10
+
 
 
+
==Create custom color table==
widget = slicer.app.layoutManager().sliceWidget(layoutName)
+
This example shows how to create a new color table, for example with inverted color range from the default Ocean color table.
view = widget.sliceView()
+
<pre>
logic = widget.sliceLogic()
+
invertedocean = slicer.vtkMRMLColorTableNode()
bounds = [0,]*6
+
invertedocean.SetTypeToUser()
logic.GetSliceBounds(bounds)
+
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)
  
for step in range(steps):
+
slicer.mrmlScene.AddNode(invertedocean)
    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)
 
 
</pre>
 
</pre>
  
== Save the scene into a new directory ==
+
==Manipulate a Slice View==
 +
 
 +
===Change slice offset===
 +
 
 +
Equivalent to moving the slider in slice view controller.
  
 
<pre>
 
<pre>
# Create a new directory where the scene will be saved into
+
layoutManager = slicer.app.layoutManager()
import time
+
red = layoutManager.sliceWidget('Red')
sceneSaveDirectory = slicer.app.temporaryPath + "/saved-scene-" + time.strftime("%Y%m%d-%H%M%S")
+
redLogic = red.sliceLogic()
if not os.access(sceneSaveDirectory, os.F_OK):
+
# Print current slice offset position
  os.makedirs(sceneSaveDirectory)
+
print(redLogic.GetSliceOffset())
 
+
# Change slice position
# Save the scene
+
redLogic.SetSliceOffset(20)
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>
 
# 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")  
 
 
</pre>
 
</pre>
  
== Save a node to file ==
+
===Change slice orientation===
  
Save a transform node to file (should work with any other node type, if file extension is set to a supported one):
+
Get 'Red' slice node and rotate around X and Y axes.
  
 
<pre>
 
<pre>
myNode = getNode("LinearTransform_3")
+
sliceNode = slicer.app.layoutManager().sliceWidget('Red').mrmlSliceNode()
 
+
sliceToRas = sliceNode.GetSliceToRAS()
myStorageNode = myNode.CreateDefaultStorageNode()
+
transform=vtk.vtkTransform()
myStorageNode.SetFileName("c:/tmp/something.tfm")
+
transform.SetMatrix(SliceToRAS)
myStorageNode.WriteData(myNode)
+
transform.RotateX(20)
 +
transform.RotateY(15)
 +
sliceToRas.DeepCopy(transform.GetMatrix())
 +
sliceNode.UpdateMatrices()
 
</pre>
 
</pre>
  
== Center the 3D View on the Scene ==
+
===Show slice views in 3D window===
<pre>
 
layoutManager = slicer.app.layoutManager()
 
threeDWidget = layoutManager.threeDWidget(0)
 
threeDView = threeDWidget.threeDView()
 
threeDView.resetFocalPoint()
 
</pre>
 
  
==Rotate the 3D View==
+
Equivalent to clicking 'eye' icon in the slice view controller.
  
 
<pre>
 
<pre>
 
layoutManager = slicer.app.layoutManager()
 
layoutManager = slicer.app.layoutManager()
threeDWidget = layoutManager.threeDWidget(0)
+
for sliceViewName in layoutManager.sliceViewNames():
threeDView = threeDWidget.threeDView()
+
  controller = layoutManager.sliceWidget(sliceViewName).sliceController()
threeDView.yaw()
+
  controller.setSliceVisible(True)
 
</pre>
 
</pre>
  
== Display text in a 3D view or slice view ==
+
===Reset field of view to show background volume maximized===
  
The easiest way to show information overlaid on a viewer is to use corner annotations.
+
Equivalent to click small rectangle button ("Adjust the slice viewer's field of view...") in the slice view controller.
  
 
<pre>
 
<pre>
view=slicer.app.layoutManager().threeDWidget(0).threeDView()
+
slicer.util.resetSliceViews()
# 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()
 
 
</pre>
 
</pre>
  
== Hide slice view annotations (DataProbe) ==
+
===Rotate slice views to volume plane===
 +
 
 +
Aligns slice views to volume axes, shows original image acquisition planes in slice views.
  
 
<pre>
 
<pre>
# Disable slice annotations immediately
+
volumeNode = slicer.util.getNode('MRHead')
slicer.modules.DataProbeInstance.infoWidget.sliceAnnotations.sliceViewAnnotationsEnabled=False
+
layoutManager = slicer.app.layoutManager()
slicer.modules.DataProbeInstance.infoWidget.sliceAnnotations.updateSliceViewFromGUI()
+
for sliceViewName in layoutManager.sliceViewNames():
# Disable slice annotations persistently (after Slicer restarts)
+
  layoutManager.sliceWidget(sliceViewName).mrmlSliceNode().RotateToVolumePlane(volumeNode)
settings = qt.QSettings()
 
settings.setValue('DataProbe/sliceViewAnnotations.enabled', 0)
 
 
</pre>
 
</pre>
  
== Turning off interpolation ==
+
===Iterate over current visible slice views, and set foreground and background images===
 
 
You can turn off interpolation for newly loaded volumes with this script from Steve Pieper.
 
  
 
<pre>
 
<pre>
def NoInterpolate(caller,event):
+
slicer.util.setSliceViewerLayers(background=mrVolume, foreground=ctVolume)
  for node in slicer.util.getNodes('*').values():
 
    if node.IsA('vtkMRMLScalarVolumeDisplayNode'):
 
      node.SetInterpolate(0)
 
 
slicer.mrmlScene.AddObserver(slicer.mrmlScene.NodeAddedEvent, NoInterpolate)
 
 
</pre>
 
</pre>
  
The below link explains how to put this in your startup script.
+
Internally, this method performs something like this:
 
 
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:
 
  
 
<pre>
 
<pre>
customLayout = """
+
layoutManager = slicer.app.layoutManager()
<layout type="vertical" split="true">
+
for sliceViewName in layoutManager.sliceViewNames():
  <item>
+
    compositeNode = layoutManager.sliceWidget(sliceViewName).sliceLogic().GetSliceCompositeNode()
  <view class="vtkMRMLViewNode" singletontag="1">
+
     # setup background volume
     <property name="viewlabel" action="default">1</property>
+
    compositeNode.SetBackgroundVolumeID(mrVolume.GetID())
  </view>
+
    # setup foreground volume
  </item>
+
    compositeNode.SetForegroundVolumeID(ctVolume.GetID())
  <item>
+
    # change opacity
  <view class="vtkMRMLSliceNode" singletontag="Red">
+
    compositeNode.SetForegroundOpacity(0.3)
    <property name="orientation" action="default">Axial</property>
+
</pre>
    <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
+
==Show a volume in slice views==
# for your custom layout ID.
 
customLayoutId=501
 
  
layoutManager = slicer.app.layoutManager()
+
Recommended:
layoutManager.layoutLogic().GetLayoutNode().AddLayoutDescription(customLayoutId, customLayout)                                       
 
  
# Switch to the new custom layout
+
<pre>
layoutManager.setLayout(customLayoutId)
+
volumeNode = slicer.util.getNode('YourVolumeNode')
 +
slicer.util.setSliceViewerLayers(background=volumeNode)
 
</pre>
 
</pre>
  
See description of standard layouts (that can be used as examples) here:
+
or
https://github.com/Slicer/Slicer/blob/master/Libs/MRML/Logic/vtkMRMLLayoutLogic.cxx
 
  
== Customize keyboard shortcuts ==
+
Show volume in all visible views where volume selection propagation is enabled:
 
 
Keyboard shortcuts can be specified for activating any Slicer feature by adding a couple of lines to your
 
[[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.
 
  
 
<pre>
 
<pre>
shortcuts = [
+
volumeNode = slicer.util.getNode('YourVolumeNode')
    ('Ctrl+b', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpRedSliceView)),
+
applicationLogic = slicer.app.applicationLogic()
    ('Ctrl+n', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpYellowSliceView)),
+
selectionNode = applicationLogic.GetSelectionNode()
    ('Ctrl+m', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpGreenSliceView)),
+
selectionNode.SetSecondaryVolumeID(volumeNode.GetID())
    ('Ctrl+,', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutFourUpView))
+
applicationLogic.PropagateForegroundVolumeSelection(0)  
    ]
 
 
 
for (shortcutKey, callback) in shortcuts:
 
    shortcut = qt.QShortcut(slicer.util.mainWindow())
 
    shortcut.setKey(qt.QKeySequence(shortcutKey))
 
    shortcut.connect( 'activated()', callback)
 
 
</pre>
 
</pre>
  
== Disable certain user interactions in slice views ==
+
or
  
For example, disable slice browsing using mouse wheel and keyboard shortcuts in the red slice viewer:
+
Show volume in selected views:
  
 
<pre>
 
<pre>
interactorStyle = slicer.app.layoutManager().sliceWidget('Red').sliceView().sliceViewInteractorStyle()
+
n = slicer.util.getNode('YourVolumeNode')
interactorStyle.SetActionEnabled(interactorStyle.BrowseSlice, False)
+
for color in ['Red', 'Yellow', 'Green']:
 +
    slicer.app.layoutManager().sliceWidget(color).sliceLogic().GetSliceCompositeNode().SetForegroundVolumeID(n.GetID())
 
</pre>
 
</pre>
  
Hide all slice view controllers:
+
==Show comparison view of all model files a folder==
 +
 
 
<pre>
 
<pre>
lm = slicer.app.layoutManager()
+
# Inputs
for sliceViewName in lm.sliceViewNames():
+
modelDir = "c:/some/folder/containing/models"
  lm.sliceWidget(sliceViewName).sliceController().setVisible(False)
+
modelFileExt = "stl"
 +
numberOfColumns = 4
 +
 
 +
import math
 +
import os
 +
modelFiles = list(f for f in os.listdir(modelDir) if f.endswith('.' + modelFileExt))
 +
 
 +
# Create a custom layout
 +
numberOfRows = int(math.ceil(len(modelFiles)/numberOfColumns))
 +
customLayoutId=567  # we pick a random id that is not used by others
 +
slicer.app.setRenderPaused(True)
 +
customLayout = '<layout type="vertical">'
 +
viewIndex = 0
 +
for rowIndex in range(numberOfRows):
 +
  customLayout += '<item><layout type="horizontal">'
 +
  for colIndex in range(numberOfColumns):
 +
    name = os.path.basename(modelFiles[viewIndex]) if viewIndex < len(modelFiles) else "compare "+str(viewIndex)
 +
    customLayout += '<item><view class="vtkMRMLViewNode" singletontag="'+name
 +
    customLayout += '"><property name="viewlabel" action="default">'+name+'</property></view></item>'
 +
    viewIndex += 1
 +
  customLayout += '</layout></item>'
 +
 
 +
customLayout += '</layout>'
 +
if not slicer.app.layoutManager().layoutLogic().GetLayoutNode().SetLayoutDescription(customLayoutId, customLayout):
 +
    slicer.app.layoutManager().layoutLogic().GetLayoutNode().AddLayoutDescription(customLayoutId, customLayout)
 +
 
 +
slicer.app.layoutManager().setLayout(customLayoutId)
 +
 
 +
# Load and show each model in a view
 +
for modelIndex, modelFile in enumerate(modelFiles):
 +
    # Show only one model in each view
 +
    name = os.path.basename(modelFile)
 +
    viewNode = slicer.mrmlScene.GetSingletonNode(name, "vtkMRMLViewNode")
 +
    viewNode.LinkedControlOn()
 +
    modelNode = slicer.util.loadModel(modelDir+"/"+modelFile)
 +
    modelNode.GetDisplayNode().AddViewNodeID(viewNode.GetID())
 +
 
 +
slicer.app.setRenderPaused(False)
 
</pre>
 
</pre>
  
Hide all 3D view controllers:
+
==Change opacity of foreground volume in slice views==
 +
 
 
<pre>
 
<pre>
lm = slicer.app.layoutManager()
+
slicer.util.setSliceViewerLayers(foregroundOpacity=0.4)
for viewIndex in range(slicer.app.layoutManager().threeDViewCount):
 
  lm.threeDWidget(0).threeDController().setVisible(False)
 
 
</pre>
 
</pre>
  
== Change default slice view orientation ==
+
or
  
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]].
+
Change opacity in a selected view
  
 
<pre>
 
<pre>
# Axial slice axes:
+
lm = slicer.app.layoutManager()
#  1 0 0
+
sliceLogic = lm.sliceWidget('Red').sliceLogic()
#  0 1 0
+
compositeNode = sliceLogic.GetSliceCompositeNode()
#  0 0 1
+
compositeNode.SetForegroundOpacity(0.4)
axialSliceToRas=vtk.vtkMatrix3x3()
+
</pre>
 
 
# 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)
 
</pre>
 
 
 
 
 
== 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]].
+
==Fit slice plane to markup fiducials==
  
 
<pre>
 
<pre>
# Set linked slice views  in all existing slice composite nodes and in the default node
+
sliceNode = slicer.mrmlScene.GetNodeByID("vtkMRMLSliceNodeRed")
sliceCompositeNodes = slicer.util.getNodesByClass('vtkMRMLSliceCompositeNode')
+
markupsNode = slicer.mrmlScene.GetFirstNodeByName("F")
defaultSliceCompositeNode = slicer.mrmlScene.GetDefaultNodeByClass('vtkMRMLSliceCompositeNode')
+
# Get markup point positions as numpy arrays
if not defaultSliceCompositeNode:
+
import numpy as np
  defaultSliceCompositeNode = slicer.mrmlScene.CreateNodeByClass('vtkMRMLSliceCompositeNode')
+
p1 = np.zeros(3)
  slicer.mrmlScene.AddDefaultNode(defaultSliceCompositeNode)
+
p2 = np.zeros(3)
sliceCompositeNodes.append(defaultSliceCompositeNode)
+
p3 = np.zeros(3)
for sliceCompositeNode in sliceCompositeNodes:
+
markupsNode.GetNthFiducialPosition(0, p1)
  sliceCompositeNode.SetLinkedControl(True)
+
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>
  
== Set crosshair jump mode to centered by default ==
+
==Save a series of images from a Slice View==
  
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 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>
crosshair=slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLCrosshairNode")
+
layoutName = 'Green'
crosshair.SetCrosshairBehavior(crosshair.CenteredJumpSlice)
+
imagePathPattern = '/tmp/image-%03d.png'
</pre>
+
steps = 10
  
== Set up custom units in slice view ruler ==
+
widget = slicer.app.layoutManager().sliceWidget(layoutName)
 +
view = widget.sliceView()
 +
logic = widget.sliceLogic()
 +
bounds = [0,]*6
 +
logic.GetSliceBounds(bounds)
  
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):
+
for step in range(steps):
 
+
     offset = bounds[4] + step/(1.*steps) * (bounds[5]-bounds[4])
<pre>
+
     logic.SetSliceOffset(offset)
lm = slicer.app.layoutManager()
+
     view.forceRender()
for sliceViewName in lm.sliceViewNames():
+
     image = qt.QPixmap.grabWidget(view).toImage()
  sliceView = lm.sliceWidget(sliceViewName).sliceView()
+
     image.save(imagePathPattern % step)
  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>
  
== Show a slice view outside the view layout ==
+
==Rasterize a model and save it to a series of image files==
 
 
<pre>
 
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
+
This example shows how to generate a stack of image files from an STL file:
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
+
inputModelFile = "/some/input/folder/SomeShape.stl"
viewWidget = slicer.qMRMLSliceWidget()
+
outputDir = "/some/output/folder"
viewWidget.sliceViewName = layoutName
+
outputVolumeLabelValue = 100
viewWidget.sliceViewLabel = layoutLabel
+
outputVolumeSpacingMm = [0.5, 0.5, 0.5]
c = viewNode.GetLayoutColor()
+
outputVolumeMarginMm = [10.0, 10.0, 10.0]
viewWidget.sliceViewColor = qt.QColor.fromRgbF(c[0],c[1],c[2])
+
viewWidget.setMRMLScene(slicer.mrmlScene)
+
# Read model
viewWidget.setMRMLSliceNode(viewNode)
+
inputModel = slicer.util.loadModel(inputModelFile)
viewWidget.show()
+
</pre>
+
# 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 3D view outside the view layout ==
+
==Save the scene into a new directory==
  
 
<pre>
 
<pre>
layoutName = "Test3DView"
+
# Create a new directory where the scene will be saved into
layoutLabel = "T3"
+
import time
# ownerNode manages this view instead of the layout manager (it can be any node in the scene)
+
sceneSaveDirectory = slicer.app.temporaryPath + "/saved-scene-" + time.strftime("%Y%m%d-%H%M%S")
viewOwnerNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScriptedModuleNode")
+
if not os.access(sceneSaveDirectory, os.F_OK):
 +
  os.makedirs(sceneSaveDirectory)
  
# Create MRML node
+
# Save the scene
viewNode = slicer.vtkMRMLViewNode()
+
if slicer.app.applicationLogic().SaveSceneToSlicerDataBundleDirectory(sceneSaveDirectory, None):
viewNode.SetName(layoutName)
+
  logging.info("Scene saved to: {0}".format(sceneSaveDirectory))
viewNode.SetLayoutName(layoutName)
+
else:
viewNode.SetLayoutLabel(layoutLabel)
+
  logging.error("Scene saving failed")  
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>
  
== Running an ITK filter in Python using SimpleITK ==
+
==Save the scene into a single MRB file==
Open the "Sample Data" module and download "MR Head", then paste the following snippet in Python interactor:
 
 
<pre>
 
<pre>
import SampleData
+
# Generate file name
import SimpleITK as sitk
+
import time
import sitkUtils
+
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")
 +
</pre>
  
# Get input volume node
+
==Save a node to file==
inputVolumeNode = SampleData.SampleDataLogic().downloadMRHead()
 
# Create new volume node for output
 
outputVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLScalarVolumeNode', 'MRHeadFiltered')
 
  
# Run processing
+
Save a transform node to file (should work with any other node type, if file extension is set to a supported one):
inputImage = sitkUtils.PullVolumeFromSlicer(inputVolumeNode)
+
 
filter = sitk.SignedMaurerDistanceMapImageFilter()
+
<pre>
outputImage = filter.Execute(inputImage)
+
myNode = getNode("LinearTransform_3")
sitkUtils.PushVolumeToSlicer(outputImage, outputVolumeNode)
 
  
# Show processing result
+
myStorageNode = myNode.CreateDefaultStorageNode()
slicer.util.setSliceViewerLayers(background=outputVolumeNode)
+
myStorageNode.SetFileName("c:/tmp/something.tfm")
 +
myStorageNode.WriteData(myNode)
 
</pre>
 
</pre>
  
More information:
+
==Override default scene save dialog==
* 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
+
Place this class in the scripted module file to override
  
== Get current mouse coordinates in a slice view ==
+
<pre>
 +
class MyModuleFileDialog ():
 +
  """This specially named class is detected by the scripted loadable
 +
  module and is the target for optional drag and drop operations.
 +
  See: Base/QTGUI/qSlicerScriptedFileDialog.h.
  
You can get 3D (RAS) coordinates of the current mouse cursor from the crosshair singleton node as shown in the example below:
+
  This class is used for overriding default scene save dialog
 +
  with simple saving the scene without asking anything.
 +
  """
  
<pre>
+
  def __init__(self,qSlicerFileDialog ):
def onMouseMoved(observer,eventid):
+
    self.qSlicerFileDialog = qSlicerFileDialog
  ras=[0,0,0]
+
    qSlicerFileDialog.fileType = 'NoFile'
  crosshairNode.GetCursorPositionRAS(ras)
+
    qSlicerFileDialog.description = 'Save scene'
  print(ras)
+
    qSlicerFileDialog.action = slicer.qSlicerFileDialog.Write
  
crosshairNode=slicer.util.getNode('Crosshair')
+
  def execDialog(self):
crosshairNode.AddObserver(slicer.vtkMRMLCrosshairNode.CursorPositionModifiedEvent, onMouseMoved)
+
    # Implement custom scene save operation here.
 +
    # Return True if saving completed successfully,
 +
    # return False if saving was cancelled.
 +
    ...
 +
    return saved
 
</pre>
 
</pre>
  
== Get DataProbe text ==
+
==Override application close behavior==
  
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.
+
When application close is requested then by default confirmation popup is displayed.
 +
To customize this behavior (for example, allow application closing without displaying default confirmation popup)
 +
an event filter can be installed for the close event on the main window:
  
 
<pre>
 
<pre>
def printDataProbe():
+
class CloseApplicationEventFilter(qt.QWidget):
   infoWidget = slicer.modules.DataProbeInstance.infoWidget
+
   def eventFilter(self, object, event):
  for layer in ('B', 'F', 'L'):
+
     if event.type() == qt.QEvent.Close:
     print(infoWidget.layerNames[layer].text, infoWidget.layerIJKs[layer].text, infoWidget.layerValues[layer].text)
+
      event.accept()
 +
      return True
 +
    return False
  
s = qt.QShortcut(qt.QKeySequence('.'), mainWindow())
+
filter = CloseApplicationEventFilter()
s.connect('activated()', printDataProbe)
+
slicer.util.mainWindow().installEventFilter(filter)
 
</pre>
 
</pre>
  
== Get reformatted image from a slice viewer as numpy array ==
+
==Center the 3D View on the Scene==
 
 
Set up 'red' slice viewer to show thick slab reconstructed from 3 slices:
 
 
<pre>
 
<pre>
sliceNodeID = 'vtkMRMLSliceNodeRed'
+
layoutManager = slicer.app.layoutManager()
 +
threeDWidget = layoutManager.threeDWidget(0)
 +
threeDView = threeDWidget.threeDView()
 +
threeDView.resetFocalPoint()
 +
</pre>
  
# Get image data from slice view
+
==Rotate the 3D 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
+
<pre>
volumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode")
+
layoutManager = slicer.app.layoutManager()
volumeNode.SetIJKToRASMatrix(sliceNode.GetXYToRAS())
+
threeDWidget = layoutManager.threeDWidget(0)
volumeNode.SetAndObserveImageData(reslicedImage)
+
threeDView = threeDWidget.threeDView()
volumeNode.CreateDefaultDisplayNodes()
+
threeDView.yaw()
volumeNode.CreateDefaultStorageNode()
 
 
 
# Get voxels as a numpy array
 
voxels = slicer.util.arrayFromVolume(volumeNode)
 
print voxels.shape
 
 
</pre>
 
</pre>
  
== Thick slab reconstruction and maximum/minimum intensity volume projections ==
+
==Display text in a 3D view or slice view==
 +
 
 +
The easiest way to show information overlaid on a viewer is to use corner annotations.
  
Set up 'red' slice viewer to show thick slab reconstructed from 3 slices:
 
 
<pre>
 
<pre>
sliceNode = slicer.mrmlScene.GetNodeByID('vtkMRMLSliceNodeRed')
+
view=slicer.app.layoutManager().threeDWidget(0).threeDView()
appLogic = slicer.app.applicationLogic()
+
# Set text to "Something"
sliceLogic = appLogic.GetSliceLogic(sliceNode)
+
view.cornerAnnotation().SetText(vtk.vtkCornerAnnotation.UpperRight,"Something")
sliceLayerLogic = sliceLogic.GetBackgroundLayer()
+
# Set color to red
reslice = sliceLayerLogic.GetReslice()
+
view.cornerAnnotation().GetTextProperty().SetColor(1,0,0)
reslice.SetSlabModeToMean()
+
# Update the view
reslice.SetSlabNumberOfSlices(10) # mean of 10 slices will computed
+
view.forceRender()
reslice.SetSlabSliceSpacingFraction(0.3) # spacing between each slice is 0.3 pixel (total 10 * 0.3 = 3 pixel neighborhood)
 
sliceNode.Modified()
 
 
</pre>
 
</pre>
  
Set up 'red' slice viewer to show maximum intensity projection (MIP):
+
To display text in slice views, replace the first line by this line (and consider hiding slice view annotations, to prevent them from overwriting the text you place there):
 +
 
 
<pre>
 
<pre>
sliceNode = slicer.mrmlScene.GetNodeByID('vtkMRMLSliceNodeRed')
+
view=slicer.app.layoutManager().sliceWidget("Red").sliceView()
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>
  
The projected image is available in a ''vtkImageData'' object by calling ''reslice.GetOutput()''.
+
==Hide slice view annotations (DataProbe)==
  
== 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()
+
# Disable slice annotations immediately
defaultModelStorageNode.SetDefaultWriteFileExtension('stl')
+
slicer.modules.DataProbeInstance.infoWidget.sliceAnnotations.sliceViewAnnotationsEnabled=False
slicer.mrmlScene.AddDefaultNode(defaultModelStorageNode)
+
slicer.modules.DataProbeInstance.infoWidget.sliceAnnotations.updateSliceViewFromGUI()
 +
# Disable slice annotations persistently (after Slicer restarts)
 +
settings = qt.QSettings()
 +
settings.setValue('DataProbe/sliceViewAnnotations.enabled', 0)
 
</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).
+
==Turning off interpolation==
  
== Change file type for saving for all volumes (with already existing storage nodes) ==
+
You can turn off interpolation for newly loaded volumes with this script from Steve Pieper.
 
 
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
+
def NoInterpolate(caller,event):
existingModelStorageNodes = slicer.util.getNodesByClass('vtkMRMLModelStorageNode')
+
  for node in slicer.util.getNodes('*').values():
for modelStorageNode in existingModelStorageNodes:
+
    if node.IsA('vtkMRMLScalarVolumeDisplayNode'):
  slicer.mrmlScene.RemoveNode(modelStorageNode)
+
      node.SetInterpolate(0)
 +
 +
slicer.mrmlScene.AddObserver(slicer.mrmlScene.NodeAddedEvent, NoInterpolate)
 
</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):
+
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:
  
 
<pre>
 
<pre>
requiredFileExtension = '.nia'
+
customLayout = """
originalFileExtension = '.nrrd'
+
<layout type="vertical" split="true">
volumeNodes = slicer.util.getNodesByClass('vtkMRMLScalarVolumeNode')
+
  <item>
for volumeNode in volumeNodes:
+
  <view class="vtkMRMLViewNode" singletontag="1">
   volumeStorageNode = volumeNode.GetStorageNode()
+
    <property name="viewlabel" action="default">1</property>
   if not volumeStorageNode:
+
  </view>
     volumeNode.AddDefaultStorageNode()
+
   </item>
     volumeStorageNode = volumeNode.GetStorageNode()
+
   <item>
     volumeStorageNode.SetFileName(volumeNode.GetName()+requiredFileExtension)
+
  <view class="vtkMRMLSliceNode" singletontag="Red">
   else:
+
     <property name="orientation" action="default">Axial</property>
    volumeStorageNode.SetFileName(volumeStorageNode.GetFileName().replace(originalFileExtension, requiredFileExtension))
+
     <property name="viewlabel" action="default">R</property>
</pre>
+
     <property name="viewcolor" action="default">#F34A33</property>
 +
  </view>
 +
   </item>
 +
</layout>
 +
"""
  
To set all volume nodes to save uncompressed by default (add this to .slicerrc.py so it takes effect for the whole session):
+
# Built-in layout IDs are all below 100, so you can choose any large random number
<pre>
+
# for your custom layout ID.
#set the default volume storage to not compress by default
+
customLayoutId=501
defaultVolumeStorageNode = slicer.vtkMRMLVolumeArchetypeStorageNode()
+
 
defaultVolumeStorageNode.SetUseCompression(0)
+
layoutManager = slicer.app.layoutManager()
slicer.mrmlScene.AddDefaultNode(defaultVolumeStorageNode)
+
layoutManager.layoutLogic().GetLayoutNode().AddLayoutDescription(customLayoutId, customLayout)                                        
logging.info("Volume nodes will be stored uncompressed by default")
+
 
 +
# Switch to the new custom layout
 +
layoutManager.setLayout(customLayoutId)
 
</pre>
 
</pre>
  
Same thing as above, but applied to all  segmentations instead of volumes:
+
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>
#set the default volume storage to not compress by default
+
# Add button to layout selector toolbar for this custom layout
defaultVolumeStorageNode = slicer.vtkMRMLSegmentationStorageNode()
+
viewToolBar = mainWindow().findChild('QToolBar', 'ViewToolBar')
defaultVolumeStorageNode.SetUseCompression(0)
+
layoutMenu = viewToolBar.widgetForAction(viewToolBar.actions()[0]).menu()
slicer.mrmlScene.AddDefaultNode(defaultVolumeStorageNode)
+
layoutSwitchActionParent = layoutMenu  # use `layoutMenu` to add inside layout list, use `viewToolBar` to add next the standard layout list
logging.info("Segmentation nodes will be stored uncompressed
+
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')
 
</pre>
 
</pre>
  
== Sequences ==
+
==Customize keyboard shortcuts==
 +
 
 +
Keyboard shortcuts can be specified for activating any Slicer feature by adding a couple of lines to your
 +
[[Documentation/{{documentation/version}}/Developers/Python_scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F|.slicerrc file]].
  
=== Concatenate all sequences in the scene into a new sequence ===
+
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.
  
 
<pre>
 
<pre>
# Get all sequence nodes in the scene
+
shortcuts = [
sequenceNodes = slicer.util.getNodesByClass('vtkMRMLSequenceNode')
+
    ('Ctrl+b', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpRedSliceView)),
mergedSequenceNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSequenceNode', 'Merged sequence')
+
    ('Ctrl+n', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpYellowSliceView)),
 
+
    ('Ctrl+m', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpGreenSliceView)),
# Merge all sequence nodes into a new sequence node
+
    ('Ctrl+,', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutFourUpView))
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
+
for (shortcutKey, callback) in shortcuts:
mergedSequenceBrowserNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSequenceBrowserNode', 'Merged')
+
    shortcut = qt.QShortcut(slicer.util.mainWindow())
mergedSequenceBrowserNode.AddSynchronizedSequenceNode(mergedSequenceNode)
+
    shortcut.setKey(qt.QKeySequence(shortcutKey))
slicer.modules.sequencebrowser.setToolBarActiveBrowserNode(mergedSequenceBrowserNode)
+
    shortcut.connect( 'activated()', callback)
# Show proxy node in slice viewers
 
mergedProxyNode = mergedSequenceBrowserNode.GetProxyNode(mergedSequenceNode)
 
slicer.util.setSliceViewerLayers(background=mergedProxyNode)
 
 
</pre>
 
</pre>
  
== Segmentations ==
+
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).
 +
<pre>
 +
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
  
=== Create a segmentation from a labelmap volume and display in 3D ===
+
shortcuts = [
 +
    ('`', lambda: cycleEffect(-1)),
 +
    ('~', lambda: cycleEffect(1)),
 +
    ]
  
<pre>
+
for (shortcutKey, callback) in shortcuts:
labelmapVolumeNode = getNode('label')
+
    shortcut = qt.QShortcut(slicer.util.mainWindow())
seg = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSegmentationNode')
+
    shortcut.setKey(qt.QKeySequence(shortcutKey))
slicer.modules.segmentations.logic().ImportLabelmapToSegmentationNode(labelmapVolumeNode, seg)
+
    shortcut.connect( 'activated()', callback)
seg.CreateClosedSurfaceRepresentation()
 
slicer.mrmlScene.RemoveNode(labelmapVolumeNode)
 
 
</pre>
 
</pre>
  
The last line is optional. It removes the original labelmap volume so that the same information is not shown twice.
+
==Customize keyboard/mouse gestures in viewers==
 +
 
 +
Example for making the 3D view rotate using right-click-and-drag:
  
=== Export labelmap node from segmentation node ===
+
<pre>
 +
threeDViewWidget = slicer.app.layoutManager().threeDWidget(0)
 +
cameraDisplayableManager = threeDViewWidget.threeDView().displayableManagerByClassName('vtkMRMLCameraDisplayableManager')
 +
cameraWidget = cameraDisplayableManager.GetCameraWidget()
  
Export smallest possible labelmap:
+
# Remove old mapping from right-click-and-drag
 +
cameraWidget.SetEventTranslationClickAndDrag(cameraWidget.WidgetStateIdle, vtk.vtkCommand.RightButtonPressEvent, vtk.vtkEvent.NoModifier,
 +
    cameraWidget.WidgetStateRotate, vtk.vtkWidgetEvent.NoEvent, vtk.vtkWidgetEvent.NoEvent)
  
<pre>
+
# Make right-click-and-drag rotate the view
seg = getNode('Segmentation')
+
cameraWidget.SetEventTranslationClickAndDrag(cameraWidget.WidgetStateIdle, vtk.vtkCommand.RightButtonPressEvent, vtk.vtkEvent.NoModifier,
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')
+
    cameraWidget.WidgetStateRotate, cameraWidget.WidgetEventRotateStart, cameraWidget.WidgetEventRotateEnd)
slicer.modules.segmentations.logic().ExportAllSegmentsToLabelmapNode(seg, labelmapVolumeNode)
 
 
</pre>
 
</pre>
  
Export labelmap that matches geometry of a chosen reference volume:
+
==Disable certain user interactions in slice views==
 +
 
 +
For example, disable slice browsing using mouse wheel and keyboard shortcuts in the red slice viewer:
  
 
<pre>
 
<pre>
seg = getNode('Segmentation')
+
interactorStyle = slicer.app.layoutManager().sliceWidget('Red').sliceView().sliceViewInteractorStyle()
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')
+
interactorStyle.SetActionEnabled(interactorStyle.BrowseSlice, False)
slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, referenceVolumeNode)
 
 
</pre>
 
</pre>
  
Export by pressing Ctrl+Shift+s key:
+
Hide all slice view controllers:
 +
<pre>
 +
lm = slicer.app.layoutManager()
 +
for sliceViewName in lm.sliceViewNames():
 +
  lm.sliceWidget(sliceViewName).sliceController().setVisible(False)
 +
</pre>
  
 +
Hide all 3D view controllers:
 
<pre>
 
<pre>
outputPath = "c:/tmp"
+
lm = slicer.app.layoutManager()
 +
for viewIndex in range(slicer.app.layoutManager().threeDViewCount):
 +
  lm.threeDWidget(0).threeDController().setVisible(False)
 +
</pre>
 +
 
 +
==Change default slice view orientation==
  
def exportLabelmap():
+
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]].
    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)
 
</pre>
 
 
 
=== Export model nodes from segmentation node ===
 
  
 
<pre>
 
<pre>
seg = getNode('Segmentation')
+
# Axial slice axes:
exportedModelsNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLModelHierarchyNode')
+
#  1 0 0
slicer.modules.segmentations.logic().ExportAllSegmentsToModelHierarchy(seg, exportedModelsNode)
+
#  0 1 0
</pre>
+
#  0 0 1
 +
axialSliceToRas=vtk.vtkMatrix3x3()
  
=== Show a segmentation in 3D ===
+
# Coronal slice axes:
Segmentation can only be shown in 3D if closed surface representation (or other 3D-displayable representation) is available. To create closed surface representation:
+
#  1 0 0
<pre>
+
#  0 0 -1
segmentation.CreateClosedSurfaceRepresentation()
+
#  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>
  
=== 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:
+
==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>
image = segmentationNode.GetBinaryLabelmapRepresentation(segmentID)
+
# 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')
 +
  defaultSliceCompositeNode.UnRegister(None)  # CreateNodeByClass is factory method, need to unregister the result to prevent memory leaks
 +
  slicer.mrmlScene.AddDefaultNode(defaultSliceCompositeNode)
 +
sliceCompositeNodes.append(defaultSliceCompositeNode)
 +
for sliceCompositeNode in sliceCompositeNodes:
 +
  sliceCompositeNode.SetLinkedControl(True)
 
</pre>
 
</pre>
Get closed surface, if it does not exist, it will return None:
+
 
 +
==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 [[Documentation/{{documentation/version}}/Developers/FAQ/Python_Scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F| .slicerrc.py file]].
 +
 
 
<pre>
 
<pre>
polydata = segmentationNode.GetClosedSurfaceRepresentation(segmentID)
+
crosshair=slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLCrosshairNode")
 +
crosshair.SetCrosshairBehavior(crosshair.CenteredJumpSlice)
 
</pre>
 
</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):
+
 
 +
==Add keyboard shortcut to jump to center or world coordinate system==
 +
 
 +
You can copy-paste this into the Python console to jump slice views to (0,0,0) position on (Ctrl+e):
 +
 
 
<pre>
 
<pre>
import vtkSegmentationCorePython as vtkSegmentationCore
+
shortcut = qt.QShortcut(qt.QKeySequence('Ctrl+e'), slicer.util.mainWindow())
outputOrientedImageData = vtkSegmentationCore.vtkOrientedImageData()
+
shortcut.connect('activated()',
slicer.vtkSlicerSegmentationsModuleLogic.GetSegmentBinaryLabelmapRepresentation(segmentationNode, segmentID, outputOrientedImageData)
+
  lambda: slicer.modules.markups.logic().JumpSlicesToLocation(0,0,0, True))
 
</pre>
 
</pre>
Same as above, for closed surface representation:
+
 
 +
==Display mouse pointer coordinates in alternative coordinate system==
 +
 
 +
The Data probe only shows coordinate values in the world coordinate system. You can make the world coordinate system mean anything you want (e.g., MNI) by applying a transform to the volume that transforms it into that space. See more details in [https://discourse.slicer.org/t/setting-an-mni-origo-to-a-volume/16164/4 here].
 +
 
 
<pre>
 
<pre>
outputPolyData = vtk.vtkPolyData()
+
def onMouseMoved(observer,eventid):
slicer.vtkSlicerSegmentationsModuleLogic.GetSegmentClosedSurfaceRepresentation(segmentationNode, segmentID, outputPolyData)
+
  mniToWorldTransformNode = getNode('LinearTransform_3')  # replace this by the name of your actual MNI to world transform
 +
  worldToMniTransform = vtk.vtkGeneralTransform()
 +
  mniToWorldTransformNode.GetTransformToWorld(worldToMniTransform)
 +
  ras=[0,0,0]
 +
  mni=[0,0,0]   
 +
  crosshairNode.GetCursorPositionRAS(ras)
 +
  worldToMniTransform.TransformPoint(ras, mni)
 +
  _ras = "; ".join([str(k) for k in ras])
 +
  _mni = "; ".join([str(k) for k in mni])
 +
  slicer.util.showStatusMessage(f"RAS={_ras}  MNI={_mni}")
 +
 
 +
crosshairNode=slicer.util.getNode('Crosshair')
 +
observationId = crosshairNode.AddObserver(slicer.vtkMRMLCrosshairNode.CursorPositionModifiedEvent, onMouseMoved)
 +
 
 +
# Run this to stop displaying values:
 +
# crosshairNode.RemoveObserver(observationId)
 
</pre>
 
</pre>
  
=== Convert all segments using default path and conversion parameters ===
+
==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):
 +
 
 
<pre>
 
<pre>
segmentationNode.CreateBinaryLabelmapRepresentation()
+
lm = slicer.app.layoutManager()
 +
for sliceViewName in lm.sliceViewNames():
 +
  sliceView = lm.sliceWidget(sliceViewName).sliceView()
 +
  displayableManager = sliceView.displayableManagerByClassName("vtkMRMLRulerDisplayableManager")
 +
  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>
  
=== Convert all segments using custom path or conversion parameters ===
+
==Show orientation marker in all views==
Change reference image geometry parameter based on an existing referenceImageData image:
+
 
 
<pre>
 
<pre>
import vtkSegmentationCorePython as vtkSegmentationCore
+
viewNodes = slicer.util.getNodesByClass('vtkMRMLAbstractViewNode')
referenceGeometry = vtkSegmentationCore.vtkSegmentationConverter.SerializeImageGeometry(referenceImageData)
+
for viewNode in viewNodes:
segmentation.SetConversionParameter(vtkSegmentationCore.vtkSegmentationConverter.GetReferenceImageGeometryParameterName(), referenceGeometry)
+
  viewNode.SetOrientationMarkerType(slicer.vtkMRMLAbstractViewNode.OrientationMarkerTypeAxes)
 
</pre>
 
</pre>
  
=== Re-convert using a modified conversion parameter ===
+
==Show a slice view outside the view layout==
Changing smoothing factor for closed surface generation:
+
 
 
<pre>
 
<pre>
import vtkSegmentationCorePython as vtkSegmentationCore
+
# layout name is used to create and identify the underlying slice node and  should be set to a value that is not used in any of the layouts owned by the layout manager
segmentation = getNode('Segmentation').GetSegmentation()
+
layoutName = "TestSlice1"
 +
layoutLabel = "TS1"
 +
layoutColor = [1.0, 1.0, 0.0]
 +
# ownerNode manages this view instead of the layout manager (it can be any node in the scene)
 +
viewOwnerNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScriptedModuleNode")
  
# Turn of surface smoothing
+
# Create MRML nodes
segmentation.SetConversionParameter('Smoothing factor','0.0')
+
viewLogic = slicer.vtkMRMLSliceLogic()
 +
viewLogic.SetMRMLScene(slicer.mrmlScene)
 +
viewNode = viewLogic.AddSliceNode(layoutName)
 +
viewNode.SetLayoutLabel(layoutLabel)
 +
viewNode.SetLayoutColor(layoutColor)
 +
viewNode.SetAndObserveParentLayoutNodeID(viewOwnerNode.GetID())
  
# Recreate representation using modified parameters (and default conversion path)
+
# Create widget
segmentation.RemoveRepresentation(vtkSegmentationCore.vtkSegmentationConverter.GetSegmentationClosedSurfaceRepresentationName())
+
viewWidget = slicer.qMRMLSliceWidget()
segmentation.CreateRepresentation(vtkSegmentationCore.vtkSegmentationConverter.GetSegmentationClosedSurfaceRepresentationName())
+
viewWidget.setMRMLScene(slicer.mrmlScene)
 +
viewWidget.setMRMLSliceNode(viewNode)
 +
sliceLogics = slicer.app.applicationLogic().GetSliceLogics()
 +
viewWidget.setSliceLogics(sliceLogics)
 +
sliceLogics.AddItem(viewWidget.sliceLogic())
 +
viewWidget.show()
 
</pre>
 
</pre>
  
=== Get centroid of a segment in world (RAS) coordinates ===
+
==Show a 3D view outside the view layout==
 
 
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')
+
# layout name is used to create and identify the underlying view node and  should be set to a value that is not used in any of the layouts owned by the layout manager
segmentId = 'Segment_1'
+
layoutName = "Test3DView"
 +
layoutLabel = "T3"
 +
layoutColor = [1.0, 1.0, 0.0]
 +
# ownerNode manages this view instead of the layout manager (it can be any node in the scene)
 +
viewOwnerNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScriptedModuleNode")
  
# Get array voxel coordinates
+
# Create MRML node
import numpy as np
+
viewLogic = slicer.vtkMRMLViewLogic()
seg=arrayFromSegment(segmentation_node, segmentId)
+
viewLogic.SetMRMLScene(slicer.mrmlScene)
# numpy array has voxel coordinates in reverse order (KJI instead of IJK)
+
viewNode = viewLogic.AddViewNode(layoutName)
# and the array is cropped to minimum size in the segmentation
+
viewNode.SetLayoutLabel(layoutLabel)
mean_KjiCropped = [coords.mean() for coords in np.nonzero(seg)]
+
viewNode.SetLayoutColor(layoutColor)
 +
viewNode.SetAndObserveParentLayoutNodeID(viewOwnerNode.GetID())
  
# Get segmentation voxel coordinates
+
# Create widget
segImage = segmentationNode.GetBinaryLabelmapRepresentation(segmentId)
+
viewWidget = slicer.qMRMLThreeDWidget()
segImageExtent = segImage.GetExtent()
+
viewWidget.setMRMLScene(slicer.mrmlScene)
# origin of the array in voxel coordinates is determined by the start extent
+
viewWidget.setMRMLViewNode(viewNode)
mean_Ijk = [mean_KjiCropped[2], mean_KjiCropped[1], mean_KjiCropped[0]] + np.array([segImageExtent[0], segImageExtent[2], segImageExtent[4]])
+
viewWidget.show()
 
+
</pre>
# 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
+
==Get displayable manager of a certain type for a certain view==
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
+
<pre>
print(mean_Ras)
+
threeDViewWidget = slicer.app.layoutManager().threeDWidget(0)
slicer.modules.markups.logic().JumpSlicesToLocation(mean_Ras[0], mean_Ras[1], mean_Ras[2], True)
+
modelDisplayableManager = threeDViewWidget.threeDView().displayableManagerByClassName('vtkMRMLModelDisplayableManager')
 +
if modelDisplayableManager is None:
 +
  logging.error('Failed to find the model displayable manager')
 
</pre>
 
</pre>
  
=== How to run segment editor effects from a script ===
+
==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:
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.
+
<pre>
 +
import SampleData
 +
import SimpleITK as sitk
 +
import sitkUtils
  
This example demonstrates how to use Segment editor effects (without GUI, using qMRMLSegmentEditorWidget):
+
# Get input volume node
 +
inputVolumeNode = SampleData.SampleDataLogic().downloadMRHead()
 +
# Create new volume node for output
 +
outputVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLScalarVolumeNode', 'MRHeadFiltered')
  
* [https://gist.github.com/lassoan/2d5a5b73645f65a5eb6f8d5f97abf31b brain tumor segmentation using grow from seeds effect]
+
# Run processing
* [https://gist.github.com/lassoan/1673b25d8e7913cbc245b4f09ed853f9 skin surface extraction using thresholding and smoothing]
+
inputImage = sitkUtils.PullVolumeFromSlicer(inputVolumeNode)
* [https://gist.github.com/lassoan/2f5071c562108dac8efe277c78f2620f mask a volume with segments and compute histogram for each region]
+
filter = sitk.SignedMaurerDistanceMapImageFilter()
* [https://gist.github.com/lassoan/5ad51c89521d3cd9c5faf65767506b37 create fat/muscle/bone segment by thresholding and report volume of each segment]
+
outputImage = filter.Execute(inputImage)
 +
sitkUtils.PushVolumeToSlicer(outputImage, outputVolumeNode)
  
This example shows how to perform operations on segmentations using VTK filters:
+
# Show processing result
* [https://gist.github.com/lassoan/7c94c334653010696b2bf96abc0ac8e7 brain tumor segmentation using grow from seeds effect]
+
slicer.util.setSliceViewerLayers(background=outputVolumeNode)
 +
</pre>
  
== Accessing views, renderers, and cameras ==
+
More information:
  
Iterate through all 3D views in current layout:
+
*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
  
<pre>
+
==Get current mouse coordinates in a slice view==
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:
+
You can get 3D (RAS) coordinates of the current mouse cursor from the crosshair singleton node as shown in the example below:
  
 
<pre>
 
<pre>
layoutManager = slicer.app.layoutManager()
+
def onMouseMoved(observer,eventid):
for sliceViewName in layoutManager.sliceViewNames():
+
   ras=[0,0,0]
   view = layoutManager.sliceWidget(sliceViewName).sliceView()
+
   crosshairNode.GetCursorPositionRAS(ras)
  sliceNode = view.mrmlSliceNode()
+
   print(ras)
  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.
+
crosshairNode=slicer.util.getNode('Crosshair')  
<pre>
+
crosshairNode.AddObserver(slicer.vtkMRMLCrosshairNode.CursorPositionModifiedEvent, onMouseMoved)
renderWindow = view.renderWindow()
 
renderers = renderWindow.GetRenderers()
 
renderer = renderers.GetItemAsObject(0)
 
camera = cameraNode.GetCamera()
 
 
</pre>
 
</pre>
  
== Hide view controller bars ==
+
==Get DataProbe text==
  
<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.
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>
 
<pre>
sliceController = slicer.app.layoutManager().sliceWidget("Red").sliceController()
+
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)
  
# hide what is not needed
+
s = qt.QShortcut(qt.QKeySequence('.'), mainWindow())
sliceController.pinButton().hide()
+
s.connect('activated()', printDataProbe)
#sliceController.viewLabel().hide()
+
</pre>
sliceController.fitToWindowToolButton().hide()
 
sliceController.sliceOffsetSlider().hide()
 
  
# add custom widgets
+
==Get axial slice as numpy array==
myButton = qt.QPushButton("My custom button")
 
sliceController.barLayout().addWidget(b)
 
</pre>
 
  
== Change 3D view background color ==
+
An axis-aligned (axial/sagittal/coronal/) slices of a volume can be extracted using simple numpy array indexing. For example:
  
 
<pre>
 
<pre>
renderWindow = slicer.app.layoutManager().threeDWidget(0).threeDView().renderWindow()
+
import SampleData
renderer = renderWindow.GetRenderers().GetFirstRenderer()
+
volumeNode = SampleData.SampleDataLogic().downloadMRHead()
renderer.SetBackground(1,0,0)
+
sliceIndex = 12
renderer.SetBackground2(1,0,0)
+
 
renderWindow.Render()
+
voxels = slicer.util.arrayFromVolume(volumeNode) # Get volume as numpy array
 +
slice = voxels[sliceIndex:,:]  # Get one slice of the volume as numpy array
 
</pre>
 
</pre>
  
== Subject hierarchy ==
+
==Get reformatted image from a slice viewer as numpy array==
==== Get the pseudo-singleton subject hierarchy node ====
 
It manages the whole hierarchy and provides functions to access and manipulate
 
  shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)
 
  
==== Create subject hierarchy item ====
+
Set up 'red' slice viewer to show thick slab reconstructed from 3 slices:
  # If it is for a data node, it is automatically created, but the create function can be used to set parent:
+
<pre>
  shNode.CreateItem(parentItemID, dataNode)
+
sliceNodeID = 'vtkMRMLSliceNodeRed'
  # 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 ====
+
# Get image data from slice view
Items in subject hierarchy are uniquely identified by integer IDs
+
sliceNode = slicer.mrmlScene.GetNodeByID(sliceNodeID)
  # Get scene item ID first because it is the root item:
+
appLogic = slicer.app.applicationLogic()
  sceneItemID = shNode.GetSceneItemID()
+
sliceLogic = appLogic.GetSliceLogic(sliceNode)
  # Get direct child by name
+
sliceLayerLogic = sliceLogic.GetBackgroundLayer()
  subjectItemID = shNode.GetItemChildWithName(sceneItemID, 'Subject_1')
+
reslice = sliceLayerLogic.GetReslice()
  # Get item for data node
+
reslicedImage = vtk.vtkImageData()
  itemID = shNode.GetItemByDataNode(dataNode)
+
reslicedImage.DeepCopy(reslice.GetOutput())
  # 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 ====
+
# Create new volume node using resliced image
  children = vtk.vtkIdList()
+
volumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode")
  shNode.GetItemChildren(parent, children)
+
volumeNode.SetIJKToRASMatrix(sliceNode.GetXYToRAS())
  for i in range(children.GetNumberOfIds()):
+
volumeNode.SetAndObserveImageData(reslicedImage)
    child = children.GetId(i)
+
volumeNode.CreateDefaultDisplayNodes()
    ...
+
volumeNode.CreateDefaultStorageNode()
  
==== Manipulate subject hierarchy item ====
+
# Get voxels as a numpy array
Instead of node operations on the individual subject hierarchy nodes, item operations are performed on the one subject hierarchy node.
+
voxels = slicer.util.arrayFromVolume(volumeNode)
  # Set item name
+
print(voxels.shape)
  shNode.SetItemName(itemID, 'NewName')
+
</pre>
  # Set item parent (reparent)
+
 
  shNode.SetItemParent(itemID, newParentItemID)
+
==Combine multiple volumes into one==
  # Set visibility of data nodes associated to items in a branch (or a leaf item)
+
 
  shNode.SetDisplayVisibilityForBranch(itemID, 1)
+
This example combines two volumes into a new one by subtracting one from the other.
  
==== Filter items in TreeView or ComboBox ====
+
<pre>
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:
+
import SampleData
    print(shTreeView.displayedItemCount()) # 5
+
[input1Volume, input2Volume] = SampleData.SampleDataLogic().downloadDentalSurgery()
    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 ===
+
import slicer.util
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).
+
a = slicer.util.arrayFromVolume(input1Volume)
 
+
b = slicer.util.arrayFromVolume(input2Volume)
  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 ==
+
# 'a' and 'b' are numpy arrays,
 +
# they can be combined using any numpy array operations
 +
# to produce the result array 'c'
 +
c = b-a
  
=== Slicer plots displayed in view layout ===
+
volumeNode = slicer.modules.volumes.logic().CloneVolume(input1Volume, "Difference")
 +
slicer.util.updateVolumeFromArray(volumeNode, c)
 +
setSliceViewerLayers(background=volumeNode)
 +
</pre>
  
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
+
==Add noise to image==
  
==== Using <code>slicer.util.plot</code> utility function ====
+
This example shows how to add simulated noise to a volume.
  
 
<pre>
 
<pre>
# Get a volume from SampleData and compute its histogram
 
 
import SampleData
 
import SampleData
 
import numpy as np
 
import numpy as np
 +
 +
# Get a sample input volume node
 
volumeNode = SampleData.SampleDataLogic().downloadMRHead()
 
volumeNode = SampleData.SampleDataLogic().downloadMRHead()
histogram = np.histogram(arrayFromVolume(volumeNode), bins=50)
 
  
chartNode = slicer.util.plot(histogram, xColumnIndex = 1)
+
# Get volume as numpy array and add noise
chartNode.SetYAxisRangeAuto(False)
+
voxels = slicer.util.arrayFromVolume(volumeNode)
chartNode.SetYAxisRange(0, 4e5)
+
voxels[:] = voxels + np.random.normal(0.0, 20.0, size=voxels.shape)
 +
slicer.util.arrayFromVolumeModified(volumeNode)
 
</pre>
 
</pre>
  
[[Image:SlicerPlot.png]]
 
  
==== Using MRML classes only ====
+
==Mask volume using segmentation==
 +
 
 +
This example shows how to blank out voxels of a volume outside all segments.
  
 
<pre>
 
<pre>
# Get a volume from SampleData
+
# Input nodes
import SampleData
+
volumeNode = getNode('MRHead')
volumeNode = SampleData.SampleDataLogic().downloadMRHead()
+
segmentationNode = getNode('Segmentation')
 +
 
 +
# Write segmentation to labelmap volume node with a geometry that matches the volume node
 +
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')
 +
slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, volumeNode)
  
# Compute histogram values
+
# Masking
 
import numpy as np
 
import numpy as np
histogram = np.histogram(arrayFromVolume(volumeNode), bins=50)
+
voxels = slicer.util.arrayFromVolume(volumeNode)
 +
mask = slicer.util.arrayFromVolume(labelmapVolumeNode)
 +
maskedVoxels = np.copy(voxels) # we don't want to modify the original volume
 +
maskedVoxels[mask==0] = 0
  
# Save results to a new table node
+
# Write masked volume to volume node and show it
tableNode=slicer.mrmlScene.AddNewNodeByClass("vtkMRMLTableNode")
+
maskedVolumeNode = slicer.modules.volumes.logic().CloneVolume(volumeNode, "Masked")
updateTableFromArray(tableNode, histogram)
+
slicer.util.updateVolumeFromArray(maskedVolumeNode, maskedVoxels)
tableNode.GetTable().GetColumn(0).SetName("Count")
+
slicer.util.setSliceViewerLayers(maskedVolumeNode)
tableNode.GetTable().GetColumn(1).SetName("Intensity")
+
</pre>
  
# Create plot
+
==Apply random deformations to image==
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
+
This example shows how to apply random translation, rotation, and deformations to a volume to simulate variation in patient positioning, soft tissue motion, and random anatomical variations.
plotChartNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLPlotChartNode")
+
Control points are placed on a regularly spaced grid and then each control point is displaced by a random amount.
plotChartNode.AddAndObservePlotSeriesNodeID(plotSeriesNode.GetID())
+
Thin-plate spline transform is computed from the original and transformed point list.
plotChartNode.YAxisRangeAutoOff()
 
plotChartNode.SetYAxisRange(0, 500000)
 
  
# Show plot in layout
+
https://gist.github.com/lassoan/428af5285da75dc033d32ebff65ba940
slicer.modules.plots.logic().ShowChartInLayout(plotChartNode)
 
</pre>
 
  
=== Using matplotlib ===
+
==Thick slab reconstruction and maximum/minimum intensity volume projections==
  
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.
+
Set up 'red' slice viewer to show thick slab reconstructed from 3 slices:
 
+
<pre>
==== Non-interactive plot ====
+
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()
 +
</pre>
  
 +
Set up 'red' slice viewer to show maximum intensity projection (MIP):
 
<pre>
 
<pre>
try:
+
sliceNode = slicer.mrmlScene.GetNodeByID('vtkMRMLSliceNodeRed')
  import matplotlib
+
appLogic = slicer.app.applicationLogic()
except ModuleNotFoundError:
+
sliceLogic = appLogic.GetSliceLogic(sliceNode)
  pip_install('matplotlib')
+
sliceLayerLogic = sliceLogic.GetBackgroundLayer()
  import matplotlib
+
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>
  
matplotlib.use('Agg')
+
The projected image is available in a ''vtkImageData'' object by calling ''reslice.GetOutput()''.
from pylab import *
 
  
t1 = arange(0.0, 5.0, 0.1)
+
==Change default file type for nodes (that have never been saved yet)==
t2 = arange(0.0, 5.0, 0.02)
+
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:
t3 = arange(0.0, 2.0, 0.01)  
+
<pre>
 +
defaultModelStorageNode = slicer.vtkMRMLModelStorageNode()
 +
defaultModelStorageNode.SetDefaultWriteFileExtension('stl')
 +
slicer.mrmlScene.AddDefaultNode(defaultModelStorageNode)
 +
</pre>
  
subplot(211)
+
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).
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)
+
==Change file type for saving for all volumes (with already existing storage nodes)==
plot(t3, cos(2*pi*t3), 'r--')
 
grid(True)
 
xlabel('time (s)')
 
ylabel('Undamped')
 
savefig('MatplotlibExample.png')
 
  
# Static image view
+
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.
pm = qt.QPixmap("MatplotlibExample.png")
+
 
imageWidget = qt.QLabel()
+
<pre>
imageWidget.setPixmap(pm)
+
# Delete existing model storage nodes so that they will be recreated with default settings
imageWidget.setScaledContents(True)
+
existingModelStorageNodes = slicer.util.getNodesByClass('vtkMRMLModelStorageNode')
imageWidget.show()
+
for modelStorageNode in existingModelStorageNodes:
 +
  slicer.mrmlScene.RemoveNode(modelStorageNode)
 
</pre>
 
</pre>
  
[[Image:MatplotlibExample.png]]
+
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):
  
==== Plot in Slicer Jupyter notebook ====
+
<pre>
 +
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))
 +
</pre>
  
 +
To set all volume nodes to save uncompressed by default (add this to .slicerrc.py so it takes effect for the whole session):
 
<pre>
 
<pre>
try:
+
#set the default volume storage to not compress by default
  import matplotlib
+
defaultVolumeStorageNode = slicer.vtkMRMLVolumeArchetypeStorageNode()
except ModuleNotFoundError:
+
defaultVolumeStorageNode.SetUseCompression(0)
  pip_install('matplotlib')
+
slicer.mrmlScene.AddDefaultNode(defaultVolumeStorageNode)
  import matplotlib
+
logging.info("Volume nodes will be stored uncompressed by default")
 +
</pre>
 +
 
 +
Same thing as above, but applied to all  segmentations instead of volumes:
 +
<pre>
 +
#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
 +
</pre>
  
matplotlib.use('Agg')
+
==Sequences==
from pylab import *
 
  
t1 = arange(0.0, 5.0, 0.1)
+
===Access voxels of a 4D volume as numpy array===
t2 = arange(0.0, 5.0, 0.02)
 
t3 = arange(0.0, 2.0, 0.01)
 
  
subplot(211)
+
<pre>
plot(t1, cos(2*pi*t1)*exp(-t1), 'bo', t2, cos(2*pi*t2)*exp(-t2), 'k')
+
# Get sequence node
grid(True)
+
import SampleData
title('A tale of 2 subplots')
+
sequenceNode = SampleData.SampleDataLogic().downloadSample('CTPCardioSeq')
ylabel('Damped')
+
# Alternatively, get the first sequence node in the scene:
 +
# sequenceNode = slicer.util.getNodesByClass('vtkMRMLSequenceNode')[0]
  
subplot(212)
+
# Get voxels of itemIndex'th volume as numpy array
plot(t3, cos(2*pi*t3), 'r--')
+
itemIndex = 5
grid(True)
+
voxelArray = slicer.util.arrayFromVolume(sequenceNode.GetNthDataNode(itemIndex))
xlabel('time (s)')
+
</pre>
ylabel('Undamped')
+
 
savefig('MatplotlibExample.png')
+
===Get index value===
display(filename='MatplotlibExample.png', type="image/png", binary=True)
+
 
 +
<pre>
 +
print("Index value of {0}th item: {1} = {2} {3}".format(
 +
  itemIndex,
 +
  sequenceNode.GetIndexName(),
 +
  sequenceNode.GetNthIndexValue(itemIndex),
 +
  sequenceNode.GetIndexUnit()))
 
</pre>
 
</pre>
  
[[Image:JupyterNotebookMatplotlibExample.png]]
+
===Browse a sequence and access currently displayed nodes===
 +
 
 +
<pre>
 +
# Get a sequence node
 +
import SampleData
 +
sequenceNode = SampleData.SampleDataLogic().downloadSample('CTPCardioSeq')
 +
 
 +
# Find corresponding sequence browser node
 +
browserNode = slicer.modules.sequences.logic().GetFirstBrowserNodeForSequenceNode(sequenceNode)
 +
 
 +
# Print sequence information
 +
print("Number of items in the sequence: {0}".format(browserNode.GetNumberOfItems()))
 +
print("Index name: {0}".format(browserNode.GetMasterSequenceNode().GetIndexName()))
 +
 
 +
# Jump to a selected sequence item
 +
browserNode.SetSelectedItemNumber(5)
 +
 
 +
# Get currently displayed volume node voxels as numpy array
 +
volumeNode = browserNode.GetProxyNode(sequenceNode)
 +
voxelArray = slicer.util.arrayFromVolume(volumeNode)
 +
```
  
==== Interactive plot using wxWidgets GUI toolkit ====
+
===Concatenate all sequences in the scene into a new sequence===
  
 
<pre>
 
<pre>
try:
+
# Get all sequence nodes in the scene
  import matplotlib
+
sequenceNodes = slicer.util.getNodesByClass('vtkMRMLSequenceNode')
  import wx
+
mergedSequenceNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSequenceNode', 'Merged sequence')
except ModuleNotFoundError:
 
  pip_install('matplotlib wxPython')
 
  import matplotlib
 
  
# Get a volume from SampleData and compute its histogram
+
# Merge all sequence nodes into a new sequence node
import SampleData
+
mergedIndexValue = 0
import numpy as np
+
for sequenceNode in sequenceNodes:
volumeNode = SampleData.SampleDataLogic().downloadMRHead()
+
    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)
 +
</pre>
 +
 
 +
==Segmentations==
 +
 
 +
===Create a segmentation from a labelmap volume and display in 3D===
 +
 
 +
<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.
 +
 
 +
===Export labelmap node from segmentation node===
 +
 
 +
Export labelmap matching reference geometry of the segmentation:
 +
 
 +
<pre>
 +
segmentationNode = getNode('Segmentation')
 +
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')
 +
slicer.modules.segmentations.logic().ExportAllSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, slicer.vtkSegmentation.EXTENT_REFERENCE_GEOMETRY)
 +
</pre>
 +
 
 +
Export smallest possible labelmap:
 +
 
 +
<pre>
 +
segmentationNode = getNode('Segmentation')
 +
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')
 +
slicer.modules.segmentations.logic().ExportAllSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode)
 +
</pre>
 +
 
 +
Export labelmap that matches geometry of a chosen reference volume:
 +
 
 +
<pre>
 +
segmentationNode = getNode('Segmentation')
 +
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')
 +
slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, referenceVolumeNode)
 +
</pre>
 +
 
 +
Export a selection of segments (identified by their names):
 +
 
 +
<pre>
 +
segmentNames = ["Prostate", "Urethra"]
 +
segmentIds = vtk.vtkStringArray()
 +
for segmentName in segmentNames:
 +
    segmentId = segmentationNode.GetSegmentation().GetSegmentIdBySegmentName(segmentName)
 +
    segmentIds.InsertNextValue(segmentId)
 +
slicer.vtkSlicerSegmentationsModuleLogic.ExportSegmentsToLabelmapNode(segmentationNode, segmentIds, labelmapVolumeNode, referenceVolumeNode)
 +
</pre>
 +
 
 +
Export to file by pressing Ctrl+Shift+S key:
 +
 
 +
<pre>
 +
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)
 +
</pre>
 +
 
 +
===Export model nodes from segmentation node===
 +
 
 +
<pre>
 +
segmentationNode = getNode("Segmentation")
 +
shNode = slicer.mrmlScene.GetSubjectHierarchyNode()
 +
exportFolderItemId = shNode.CreateFolderItem(shNode.GetSceneItemID(), "Segments")
 +
slicer.modules.segmentations.logic().ExportAllSegmentsToModels(segmentationNode, exportFolderItemId)
 +
</pre>
 +
 
 +
===Create a hollow model from boundary of solid segment===
 +
 
 +
In most cases, the most robust and flexible tool for creating empty shell models (e.g., vessel wall model from contrast agent segmentation) is the "Hollow" effect in Segment Editor module. However, for very thin shells, extrusion of the exported surface mesh representation may be just as robust and require less memory and computation time. In this case it may be a better approach to to export the segment to a mesh and extrude it along surface normal direction:
 +
 
 +
Example using Dynamic Modeler module (allows real-time update of parameters, using GUI in Dynamic Modeler module):
 +
 
 +
<pre>
 +
segmentationNode = getNode("Segmentation")
 +
 
 +
# Export segments to models
 +
shNode = slicer.mrmlScene.GetSubjectHierarchyNode()
 +
exportFolderItemId = shNode.CreateFolderItem(shNode.GetSceneItemID(), "Segments")
 +
slicer.modules.segmentations.logic().ExportAllSegmentsToModels(segmentationNode, exportFolderItemId)
 +
segmentModels = vtk.vtkCollection()
 +
shNode.GetDataNodesInBranch(exportFolderItemId, segmentModels)
 +
# Get exported model of first segment
 +
modelNode = segmentModels.GetItemAsObject(0)
 +
 
 +
# Set up Hollow tool
 +
hollowModeler = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLDynamicModelerNode")
 +
hollowModeler.SetToolName("Hollow")
 +
hollowModeler.SetNodeReferenceID("Hollow.InputModel", modelNode.GetID())
 +
hollowedModelNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLModelNode")  # this node will store the hollow model
 +
hollowModeler.SetNodeReferenceID("Hollow.OutputModel", hollowedModelNode.GetID())
 +
hollowModeler.SetAttribute("ShellThickness", "2.5")  # grow outside
 +
hollowModeler.SetContinuousUpdate(True)  # auto-update output model if input parameters are changed
 +
 
 +
# Hide inputs, show output
 +
segmentation.GetDisplayNode().SetVisibility(False)
 +
modelNode.GetDisplayNode().SetVisibility(False)
 +
hollowedModelNode.GetDisplayNode().SetOpacity(0.5)
 +
</pre>
 +
 
 +
Example using VTK filters:
 +
 
 +
<pre>
 +
# Get closed surface representation of the segment
 +
shellThickness = 3.0  # mm
 +
segmentationNode = getNode('Segmentation')
 +
segmentationNode.CreateClosedSurfaceRepresentation()
 +
polyData = segmentationNode.GetClosedSurfaceInternalRepresentation('Segment_1')
 +
 
 +
# Create shell
 +
extrude = vtk.vtkLinearExtrusionFilter()
 +
extrude.SetInputData(polyData)
 +
extrude.SetExtrusionTypeToNormalExtrusion()
 +
extrude.SetScaleFactor(shellThickness)
 +
 
 +
# Compute consistent surface normals
 +
triangle_filter = vtk.vtkTriangleFilter()
 +
triangle_filter.SetInputConnection(extrude.GetOutputPort())
 +
normals = vtk.vtkPolyDataNormals()
 +
normals.SetInputConnection(triangle_filter.GetOutputPort())
 +
normals.FlipNormalsOn()
 +
 
 +
# Save result into new model node
 +
slicer.modules.models.logic().AddModel(normals.GetOutputPort())
 +
</pre>
 +
 
 +
===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 = slicer.vtkOrientedImageData()
 +
segmentationNode.GetBinaryLabelmapRepresentation(segmentID, image)
 +
</pre>
 +
Get closed surface, if it does not exist, it will return None:
 +
<pre>
 +
outputPolyData = vtk.vtkPolyData()
 +
segmentationNode.GetClosedSurfaceRepresentation(segmentID, outputPolyData)
 +
</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>
 +
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>
 +
 
 +
===Convert all segments using default path and conversion parameters===
 +
<pre>
 +
segmentationNode.CreateBinaryLabelmapRepresentation()
 +
</pre>
 +
 
 +
===Convert all segments using custom path or conversion parameters===
 +
Change reference image geometry parameter based on an existing referenceImageData image:
 +
<pre>
 +
referenceGeometry = slicer.vtkSegmentationConverter.SerializeImageGeometry(referenceImageData)
 +
segmentation.SetConversionParameter(slicer.vtkSegmentationConverter.GetReferenceImageGeometryParameterName(), referenceGeometry)
 +
</pre>
 +
 
 +
===Re-convert using a modified conversion parameter===
 +
Changing smoothing factor for closed surface generation:
 +
<pre>
 +
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())
 +
</pre>
 +
 
 +
===Create keyboard shortcut for toggling sphere brush for paint and erase effects===
 +
 
 +
<pre>
 +
def toggleSphereBrush():
 +
    segmentEditorWidget = slicer.modules.segmenteditor.widgetRepresentation().self().editor
 +
    paintEffect = segmentEditorWidget.effectByName("Paint")
 +
    isSphere = paintEffect.integerParameter('BrushSphere')
 +
    # BrushSphere is "common" parameter (shared between paint and erase)
 +
    paintEffect.setCommonParameter("BrushSphere", 0 if isSphere else 1)
 +
 
 +
shortcut = qt.QShortcut(slicer.util.mainWindow())
 +
shortcut.setKey(qt.QKeySequence("s"))
 +
shortcut.connect('activated()', toggleSphereBrush)
 +
</pre>
 +
 
 +
===Customize list of displayed Segment editor effects===
 +
 
 +
Only show Paint and Erase effects:
 +
 
 +
<pre>
 +
segmentEditorWidget = slicer.modules.segmenteditor.widgetRepresentation().self().editor
 +
segmentEditorWidget.setEffectNameOrder(['Paint', 'Erase'])
 +
segmentEditorWidget.unorderedEffectsVisible = False
 +
</pre>
 +
 
 +
Show list of all available effect names:
 +
 
 +
<pre>
 +
segmentEditorWidget = slicer.modules.segmenteditor.widgetRepresentation().self().editor
 +
print(segmentEditorWidget.availableEffectNames())
 +
</pre>
 +
 
 +
===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.
 +
 
 +
<pre>
 +
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)
 +
</pre>
 +
 
 +
===Get histogram of a segmented region===
 +
 
 +
<pre>
 +
# Generate input data
 +
################################################
 +
 
 +
# Load master volume
 +
import SampleData
 +
sampleDataLogic = SampleData.SampleDataLogic()
 +
masterVolumeNode = sampleDataLogic.downloadMRBrainTumor1()
 +
 
 +
# Create segmentation
 +
segmentationNode = slicer.vtkMRMLSegmentationNode()
 +
slicer.mrmlScene.AddNode(segmentationNode)
 +
segmentationNode.CreateDefaultDisplayNodes() # only needed for display
 +
segmentationNode.SetReferenceImageGeometryParameterFromVolumeNode(masterVolumeNode)
 +
 
 +
# Create segment
 +
tumorSeed = vtk.vtkSphereSource()
 +
tumorSeed.SetCenter(-6, 30, 28)
 +
tumorSeed.SetRadius(25)
 +
tumorSeed.Update()
 +
segmentationNode.AddSegmentFromClosedSurfaceRepresentation(tumorSeed.GetOutput(), "Segment A", [1.0,0.0,0.0])
 +
 
 +
# Compute histogram
 +
################################################
 +
 
 +
labelValue = 1  # label value of first segment
 +
 
 +
# Get segmentation as labelmap volume node
 +
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')
 +
slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, masterVolumeNode)
 +
 
 +
# Extract all voxels of the segment as numpy array
 +
volumeArray = slicer.util.arrayFromVolume(masterVolumeNode)
 +
labelArray = slicer.util.arrayFromVolume(labelmapVolumeNode)
 +
segmentVoxels = volumeArray[labelArray==labelValue]
 +
 
 +
# Compute histogram
 +
import numpy as np
 +
histogram = np.histogram(segmentVoxels, bins=50)
 +
 
 +
# Plot histogram
 +
################################################
 +
 
 +
slicer.util.plot(histogram, xColumnIndex = 1)
 +
</pre>
 +
 
 +
===Get segments visible at a selected position===
 +
 
 +
Show in the console names of segments visible at a markups fiducial position:
 +
 
 +
<pre>
 +
segmentationNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLSegmentationNode")
 +
markupsFiducialNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLMarkupsFiducialNode")
 +
sliceViewLabel = "Red"  # any slice view where segmentation node is visible works
 +
 
 +
def printSegmentNames(unused1=None, unused2=None):
 +
   
 +
    sliceViewWidget = slicer.app.layoutManager().sliceWidget(sliceViewLabel)
 +
    segmentationsDisplayableManager = sliceViewWidget.sliceView().displayableManagerByClassName('vtkMRMLSegmentationsDisplayableManager2D')
 +
    ras = [0,0,0]
 +
    markupsFiducialNode.GetNthControlPointPositionWorld(0, ras)
 +
    segmentIds = vtk.vtkStringArray()
 +
    segmentationsDisplayableManager.GetVisibleSegmentsForPosition(ras, segmentationNode.GetDisplayNode(), segmentIds)
 +
    for idIndex in range(segmentIds.GetNumberOfValues()):
 +
        segment = segmentationNode.GetSegmentation().GetSegment(segmentIds.GetValue(idIndex))
 +
        print('Segment found at position {0}: {1}'.format(ras, segment.GetName()))
 +
 
 +
# Observe markup node changes
 +
markupsFiducialNode.AddObserver(slicer.vtkMRMLMarkupsPlaneNode.PointModifiedEvent, printSegmentNames)
 +
printSegmentNames()
 +
</pre>
 +
 
 +
===Set default segmentation options===
 +
 
 +
Allow segments to overlap each other by default:
 +
 
 +
<pre>
 +
defaultSegmentEditorNode = slicer.vtkMRMLSegmentEditorNode()
 +
defaultSegmentEditorNode.SetOverwriteMode(slicer.vtkMRMLSegmentEditorNode.OverwriteNone)
 +
slicer.mrmlScene.AddDefaultNode(defaultSegmentEditorNode)
 +
</pre>
 +
 
 +
To always make this the default, add the lines above to your
 +
[[Documentation/{{documentation/version}}/Developers/Python_scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F|.slicerrc file]].
 +
 
 +
===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, it is recommended to use the effect by instantiating a qMRMLSegmentEditorWidget or use/extract processing logic of the effect and use that from a script.
 +
 
 +
====Use Segment editor effects from script (qMRMLSegmentEditorWidget)====
 +
 
 +
Examples:
 +
 
 +
*[https://gist.github.com/lassoan/2d5a5b73645f65a5eb6f8d5f97abf31b brain tumor segmentation using grow from seeds effect]
 +
*[https://gist.github.com/lassoan/ef30bc27a22a648ead7f82243f5cc7d5 AI-assisted brain tumor segmentation]
 +
*[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]
 +
*[https://gist.github.com/lassoan/4d0b94bda52d5b099432e424e03aa2b1 segment cranial cavity automatically in dry bone skull CT]
 +
*[https://gist.github.com/lassoan/84d1f9a093dbb6a46c0fcc89279d8088 remove patient table from CT image]
 +
 
 +
Description of effect parameters are available [https://slicer.readthedocs.io/en/latest/developer_guide/modules/segmenteditor.html#effect-parameters here].
 +
 
 +
====Use logic of effect from a script====
 +
 
 +
This example shows how to perform operations on segmentations using VTK filters ''extracted'' from an effect:
 +
 
 +
*[https://gist.github.com/lassoan/7c94c334653010696b2bf96abc0ac8e7 brain tumor segmentation using grow from seeds effect]
 +
 
 +
===Process segment using a VTK filter===
 +
 
 +
This example shows how to apply a VTK filter to a segment that dilates the image by a specified margin.
 +
 
 +
<pre>
 +
segmentationNode = getNode('Segmentation')
 +
segmentId = "Segment_1"
 +
kernelSize = [3,1,5]
 +
 
 +
# Export segment as vtkImageData (via temporary labelmap volume node)
 +
segmentIds = vtk.vtkStringArray()
 +
segmentIds.InsertNextValue(segmentId)
 +
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')
 +
slicer.modules.segmentations.logic().ExportSegmentsToLabelmapNode(segmentationNode, segmentIds, labelmapVolumeNode)
 +
 
 +
# Process segmentation
 +
segmentImageData = labelmapVolumeNode.GetImageData()
 +
erodeDilate = vtk.vtkImageDilateErode3D()
 +
erodeDilate.SetInputData(segmentImageData)
 +
erodeDilate.SetDilateValue(1)
 +
erodeDilate.SetErodeValue(0)
 +
erodeDilate.SetKernelSize(*kernelSize)
 +
erodeDilate.Update()
 +
segmentImageData.DeepCopy(erodeDilate.GetOutput())
 +
 
 +
# Import segment from vtkImageData
 +
slicer.modules.segmentations.logic().ImportLabelmapToSegmentationNode(labelmapVolumeNode, segmentationNode, segmentIds)
 +
 
 +
# Cleanup temporary nodes
 +
slicer.mrmlScene.RemoveNode(labelmapVolumeNode.GetDisplayNode().GetColorNode())
 +
slicer.mrmlScene.RemoveNode(labelmapVolumeNode)
 +
</pre>
 +
 
 +
===Get information from segmentation nrrd file header===
 +
 
 +
You can use this code snippet to get information from segmentation (.seg.nrrd), for example when creating numpy arrays for generating training data for deep learning networks. This script can be used in any Python environment, not just inside Slicer.
 +
 
 +
<pre>
 +
# pip_install('pynrrd')
 +
 
 +
def read_segmentation_info(filename):
 +
    import nrrd
 +
    header = nrrd.read_header(filename)
 +
    segmentation_info = {}
 +
    segments = []
 +
    segment_index = 0
 +
    while True:
 +
        prefix = "Segment{0}_".format(segment_index)
 +
        if not prefix + "ID" in header.keys():
 +
            break
 +
        segment = {}
 +
        segment["index"] = segment_index
 +
        segment["color"] = [float(i) for i in header[prefix + "Color"].split(" ")]  # Segment0_Color:=0.501961 0.682353 0.501961
 +
        segment["colorAutoGenerated"] = int(header[prefix + "ColorAutoGenerated"]) != 0  # Segment0_ColorAutoGenerated:=1
 +
        segment["extent"] = [int(i) for i in header[prefix + "Extent"].split(" ")]  # Segment0_Extent:=68 203 53 211 24 118
 +
        segment["id"] = header[prefix + "ID"]  # Segment0_ID:=Segment_1
 +
        segment["labelValue"] = int(header[prefix + "LabelValue"])  # Segment0_LabelValue:=1
 +
        segment["layer"] = int(header[prefix + "Layer"])  # Segment0_Layer:=0
 +
        segment["name"] = header[prefix + "Name"]  # Segment0_Name:=Segment_1
 +
        segment["nameAutoGenerated"] = int(header[prefix + "NameAutoGenerated"]) != 0  # Segment0_NameAutoGenerated:=1
 +
        # Segment0_Tags:=Segmentation.Status:inprogress|TerminologyEntry:Segmentation category and type - 3D Slicer General Anatomy list
 +
        # ~SCT^85756007^Tissue~SCT^85756007^Tissue~^^~Anatomic codes - DICOM master list~^^~^^|
 +
        tags = {}
 +
        tags_str = header[prefix + "Tags"].split("|")
 +
        for tag_str in tags_str:
 +
            tag_str = tag_str.strip()
 +
            if not tag_str:
 +
                continue
 +
            key, value = tag_str.split(":", maxsplit=1)
 +
            tags[key] = value
 +
        segment["tags"] = tags
 +
        segments.append(segment)
 +
        segment_index += 1
 +
    segmentation_info["segments"] = segments
 +
    return segmentation_info
 +
 
 +
def segment_from_name(segmentation_info, segment_name):
 +
    for segment in segmentation_info["segments"]:
 +
        if segment_name == segment["name"]:
 +
            return segment
 +
    raise KeyError('segment not found by name ' + segment_name)
 +
 
 +
def segment_names(segmentation_info):
 +
    names = []
 +
    for segment in segmentation_info["segments"]:
 +
        names.append(segment["name"])
 +
    return names
 +
 
 +
def extract_segments(voxels, header, segmentation_info, segment_names_to_label_values):
 +
    import numpy as np
 +
    # Create empty array from last 3 dimensions (output will be flattened to a 3D array)
 +
    output_voxels = np.zeros(voxels.shape[-3:])
 +
    # Copy non-segmentation fields to the extracted header
 +
    output_header = {}
 +
    for key in header.keys():
 +
        if not re.match("^Segment[0-9]+_.+", key):
 +
            output_header[key] = header[key]
 +
    # Copy extracted segments
 +
    dims = len(voxels.shape)
 +
    for output_segment_index, segment_name_to_label_value in enumerate(segment_names_to_label_values):
 +
        # Copy relabeled voxel data
 +
        segment = segment_from_name(segmentation_info, segment_name_to_label_value[0])
 +
        input_label_value = segment["labelValue"]
 +
        output_label_value = segment_name_to_label_value[1]
 +
        if dims == 3:
 +
            output_voxels[voxels == input_label_value] = output_label_value
 +
        elif dims == 4:
 +
            inputLayer = segment["layer"]
 +
            output_voxels[voxels[inputLayer,:,:,:] == input_label_value] = output_label_value
 +
        else:
 +
            raise ValueError("Voxel array dimension is invalid")
 +
        # Copy all segment fields corresponding to this segment
 +
        for key in header.keys():
 +
            prefix = "Segment{0}_".format(segment["index"])
 +
            matched = re.match("^"+prefix+"(.+)", key)
 +
            if matched:
 +
                field_name = matched.groups()[0]
 +
                if field_name == "LabelValue":
 +
                    value = output_label_value
 +
                elif field_name == "Layer":
 +
                    # output is a single layer (3D volume)
 +
                    value = 0
 +
                else:
 +
                    value = header[key]
 +
                output_header["Segment{0}_".format(output_segment_index) + field_name] = value
 +
    # Remove unnecessary 4th dimension (volume is collapsed into 3D)
 +
    if dims == 4:
 +
        # Remove "none" from "none (0,1,0) (0,0,-1) (-1.2999954223632812,0,0)"
 +
        output_header["space directions"] = output_header["space directions"][-3:,:]
 +
        # Remove "list" from "list domain domain domain"
 +
        output_header["kinds"] = output_header["kinds"][-3:]
 +
    return output_voxels, output_header
 +
 
 +
# Read segmentation and show some information about segments
 +
filename = "c:/Users/andra/OneDrive/Projects/SegmentationPynrrd/SegmentationOverlapping.seg.nrrd"
 +
segmentation_info = read_segmentation_info(filename)
 +
number_of_segments = len(segmentation_info["segments"])
 +
names = segment_names(segmentation_info)
 +
label0 = segment_from_name(segmentation_info, names[0])["labelValue"]
 +
print("Number of segments: " + str())
 +
print("Segment names: " + str(names))
 +
print("Label value of {0}: {1}".format(names[0], label0))
 +
 
 +
# Extract selected segments with chosen label values
 +
extracted_filename = "c:/Users/andra/OneDrive/Projects/SegmentationPynrrd/SegmentationExtracted.seg.nrrd"
 +
voxels, header = nrrd.read(filename)
 +
segment_list = [("Segment_1", 10), ("Segment_3", 12), ("Segment_4", 6)]
 +
extracted_voxels, extracted_header = extract_segments(voxels, header, segmentation_info, segment_list)
 +
nrrd.write(extracted_filename, extracted_voxels, extracted_header)
 +
</pre>
 +
 
 +
==Quantifying segments==
 +
 
 +
===Get centroid of each segment===
 +
 
 +
Place a markups fiducial point at the centroid of each segment.
 +
 
 +
<pre>
 +
segmentationNode = getNode('Segmentation')
 +
 
 +
# Compute centroids
 +
import SegmentStatistics
 +
segStatLogic = SegmentStatistics.SegmentStatisticsLogic()
 +
segStatLogic.getParameterNode().SetParameter("Segmentation", segmentationNode.GetID())
 +
segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.centroid_ras.enabled", str(True))
 +
segStatLogic.computeStatistics()
 +
stats = segStatLogic.getStatistics()
 +
 
 +
# Place a markup point in each centroid
 +
markupsNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsFiducialNode")
 +
markupsNode.CreateDefaultDisplayNodes()
 +
for segmentId in stats['SegmentIDs']:
 +
    centroid_ras = stats[segmentId,"LabelmapSegmentStatisticsPlugin.centroid_ras"]
 +
    segmentName = segmentationNode.GetSegmentation().GetSegment(segmentId).GetName()
 +
    markupsNode.AddFiducialFromArray(centroid_ras, segmentName)
 +
</pre>
 +
 
 +
===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>
 +
 
 +
Complete list of available parameters can be obtained by running <code>segStatLogic.getParameterNode().GetParameterNames()</code>.
 +
 
 +
==Markups==
 +
 
 +
===Load markups fiducial list from file===
 +
 
 +
Markups fiducials can be loaded from file:
 +
 
 +
<pre>
 +
slicer.util.loadMarkupsFiducialList('/path/to/list/F.fcsv')
 +
</pre>
 +
 
 +
===Adding Fiducials Programatically===
 +
 
 +
Markups fiducials can be added to the currently active list from the python console by using the following module logic command:
 +
 
 +
<pre>
 +
slicer.modules.markups.logic().AddFiducial()
 +
</pre>
 +
 
 +
The command with no arguments will place a new fiducial at the origin. You can also pass it an initial location:
 +
 
 +
<pre>
 +
slicer.modules.markups.logic().AddFiducial(1.0, -2.0, 3.3)
 +
</pre>
 +
 
 +
===Add a button to module GUI to activate fiducial placement===
 +
 
 +
This code snippet creates a toggle button, which activates fiducial placement when pressed (and deactivates when released).
 +
 
 +
The [http://apidocs.slicer.org/master/classqSlicerMarkupsPlaceWidget.html qSlicerMarkupsPlaceWidget widget] can automatically activate placement of multiple points and can show buttons for deleting points, changing colors, lock, and hide points.
 +
 
 +
<pre>
 +
w=slicer.qSlicerMarkupsPlaceWidget()
 +
w.setMRMLScene(slicer.mrmlScene)
 +
markupsNodeID = slicer.modules.markups.logic().AddNewFiducialNode()
 +
w.setCurrentNode(slicer.mrmlScene.GetNodeByID(markupsNodeID))
 +
# Hide all buttons and only show place button
 +
w.buttonsVisible=False
 +
w.placeButton().show()
 +
w.show()
 +
</pre>
 +
 
 +
===Adding Fiducials via Mouse Clicks===
 +
 
 +
You can also set the mouse mode into Markups fiducial placement by calling:
 +
 
 +
<pre>
 +
placeModePersistence = 1
 +
slicer.modules.markups.logic().StartPlaceMode(placeModePersistence)
 +
</pre>
 +
 
 +
A lower level way to do this is via the selection and interaction nodes:
 +
 
 +
<pre>
 +
selectionNode = slicer.mrmlScene.GetNodeByID("vtkMRMLSelectionNodeSingleton")
 +
selectionNode.SetReferenceActivePlaceNodeClassName("vtkMRMLMarkupsFiducialNode")
 +
interactionNode = slicer.mrmlScene.GetNodeByID("vtkMRMLInteractionNodeSingleton")
 +
placeModePersistence = 1
 +
interactionNode.SetPlaceModePersistence(placeModePersistence)
 +
# mode 1 is Place, can also be accessed via slicer.vtkMRMLInteractionNode().Place
 +
interactionNode.SetCurrentInteractionMode(1)
 +
</pre>
 +
 
 +
To switch back to view transform once you're done placing fiducials:
 +
 
 +
<pre>
 +
interactionNode = slicer.mrmlScene.GetNodeByID("vtkMRMLInteractionNodeSingleton")
 +
interactionNode.SwitchToViewTransformMode()
 +
# also turn off place mode persistence if required
 +
interactionNode.SetPlaceModePersistence(0)
 +
</pre>
 +
 
 +
===Access to Fiducial Properties===
 +
 
 +
Each vtkMRMLMarkupsFiducialNode has a vector of points in it which can be accessed from python:
 +
 
 +
<pre>
 +
fidNode = getNode("vtkMRMLMarkupsFiducialNode1")
 +
n = fidNode.AddFiducial(4.0, 5.5, -6.0)
 +
fidNode.SetNthFiducialLabel(n, "new label")
 +
# each markup is given a unique id which can be accessed from the superclass level
 +
id1 = fidNode.GetNthMarkupID(n)
 +
# manually set the position
 +
fidNode.SetNthFiducialPosition(n, 6.0, 7.0, 8.0)
 +
# set the label
 +
fidNode.SetNthFiducialLabel(n, "New label")
 +
# set the selected flag, only selected = 1 fiducials will be passed to CLIs
 +
fidNode.SetNthFiducialSelected(n, 1)
 +
# set the visibility flag
 +
fidNode.SetNthFiducialVisibility(n, 0)  
 +
</pre>
 +
 
 +
You can loop over the fiducials in a list and get the coordinates:
 +
 
 +
<pre>
 +
fidList = slicer.util.getNode('F')
 +
numFids = fidList.GetNumberOfFiducials()
 +
for i in range(numFids):
 +
 ras = [0,0,0]
 +
 fidList.GetNthFiducialPosition(i,ras)
 +
 # the world position is the RAS position with any transform matrices applied
 +
 world = [0,0,0,0]
 +
 fidList.GetNthFiducialWorldCoordinates(0,world)
 +
 print(i,": RAS =",ras,", world =",world)
 +
</pre>
 +
 
 +
You can also look at the sample code in the [https://github.com/Slicer/Slicer/blob/master/Modules/Scripted/Endoscopy/Endoscopy.py#L287 Endoscopy module] to see how python is used to access fiducials from a scripted module.
 +
 
 +
==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(myButton)
 +
</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>
 +
 
 +
==Change view axis labels==
 +
 
 +
<pre>
 +
labels = ['x', 'X', 'y', 'Y', 'z', 'Z']
 +
viewNode = slicer.app.layoutManager().threeDWidget(0).mrmlViewNode()
 +
# for slice view:
 +
# viewNode = slicer.app.layoutManager().sliceWidget('Red').mrmlSliceNode()
 +
for index, label in enumerate(labels):
 +
  viewNode.SetAxisLabel(index, label)
 +
</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) # Add a third argument with value True for recursive query
 +
  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