Documentation/Nightly/ScriptRepository
For the latest Slicer documentation, visit the read-the-docs. |
Contents
- 1 Community-contributed modules
- 2 Community-contributed examples
- 2.1 Capture
- 2.2 Launching Slicer
- 2.3 Load volume from file
- 2.4 Show volume rendering automatically when a volume is loaded
- 2.5 Automatically load volumes that are copied into a folder
- 2.6 DICOM
- 2.6.1 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:
- 2.6.2 How to access DICOM tags nested in a sequence
- 2.6.3 How to access tag of a volume loaded from DICOM? For example, get the patient position stored in a volume:
- 2.6.4 How to access tag of an item in the Subject Hierachy tree? For example, get the content time tag of a structure set:
- 2.6.5 How to get path and filename of a loaded DICOM volume?
- 2.6.6 How can I convert DICOM to NRRD on the command line?
- 2.6.7 Export a volume to DICOM file format
- 2.6.8 Customize table columns in DICOM browser
- 2.7 Toolbar functions
- 2.8 Manipulating objects in the slice viewer
- 2.9 Measure angle between two slice planes
- 2.10 Set slice position and orientation from 3 markup fiducials
- 2.11 Set slice position and orientation from a normal vector and position
- 2.12 Switching to markup fiducial placement mode
- 2.13 Change markup fiducial display properties
- 2.14 Get a notification if a markup point position is modified
- 2.15 Get a notification if a transform is modified
- 2.16 Show a context menu when a markup point is clicked in a slice or 3D view
- 2.17 Write markup positions to JSON file
- 2.18 Write annotation ROI to JSON file
- 2.19 Show a simple surface mesh as a model node
- 2.20 Add a texture mapped plane to the scene as a model
- 2.21 Get scalar values at surface of a model
- 2.22 Select cells of a using markups fiducial points
- 2.23 Export entire scene as VRML
- 2.24 Export model to Blender, including color by scalar
- 2.25 Export a tract (FiberBundle) to Blender, including color
- 2.26 Iterate over tract (FiberBundle) streamline points
- 2.27 Clone a node
- 2.28 Clone a volume
- 2.29 Create a new volume
- 2.30 Get value of a volume at specific voxel coordinates
- 2.31 Modify voxels in a volume
- 2.32 Get volume voxel coordinates from markup fiducial RAS coordinates
- 2.33 Get markup fiducial RAS coordinates from volume voxel coordinates
- 2.34 Get the values of all voxels for a label value
- 2.35 Access values in a DTI tensor volume
- 2.36 Change window/level (brightness/contrast) or colormap of a volume
- 2.37 Make mouse left-click and drag on the image adjust window/level
- 2.38 Create custom color table
- 2.39 Manipulate a Slice View
- 2.40 Show a volume in slice views
- 2.41 Change opacity of foreground volume in slice views
- 2.42 Fit slice plane to markup fiducials
- 2.43 Save a series of images from a Slice View
- 2.44 Rasterize a model and save it to a series of image files
- 2.45 Save the scene into a new directory
- 2.46 Save the scene into a single MRB file
- 2.47 Save a node to file
- 2.48 Center the 3D View on the Scene
- 2.49 Rotate the 3D View
- 2.50 Display text in a 3D view or slice view
- 2.51 Hide slice view annotations (DataProbe)
- 2.52 Turning off interpolation
- 2.53 Customize viewer layout
- 2.54 Customize keyboard shortcuts
- 2.55 Disable certain user interactions in slice views
- 2.56 Change default slice view orientation
- 2.57 Set all slice views linked by default
- 2.58 Set crosshair jump mode to centered by default
- 2.59 Set up custom units in slice view ruler
- 2.60 Show a slice view outside the view layout
- 2.61 Show a 3D view outside the view layout
- 2.62 Get displayable manager of a certain type for a certain view
- 2.63 Running an ITK filter in Python using SimpleITK
- 2.64 Get current mouse coordinates in a slice view
- 2.65 Get DataProbe text
- 2.66 Get reformatted image from a slice viewer as numpy array
- 2.67 Combine multiple volumes into one
- 2.68 Thick slab reconstruction and maximum/minimum intensity volume projections
- 2.69 Change default file type for nodes (that have never been saved yet)
- 2.70 Change file type for saving for all volumes (with already existing storage nodes)
- 2.71 Sequences
- 2.72 Segmentations
- 2.72.1 Create a segmentation from a labelmap volume and display in 3D
- 2.72.2 Export labelmap node from segmentation node
- 2.72.3 Export model nodes from segmentation node
- 2.72.4 Show a segmentation in 3D
- 2.72.5 Get a representation of a segment
- 2.72.6 Convert all segments using default path and conversion parameters
- 2.72.7 Convert all segments using custom path or conversion parameters
- 2.72.8 Re-convert using a modified conversion parameter
- 2.72.9 Get centroid of a segment in world (RAS) coordinates
- 2.72.10 How to run segment editor effects from a script
- 2.73 Accessing views, renderers, and cameras
- 2.74 Hide view controller bars
- 2.75 Customize widgets in view controller bars
- 2.76 Change 3D view background color
- 2.77 Hide Slicer logo from main window (to increase space)
- 2.78 Subject hierarchy
- 2.78.1 Get the pseudo-singleton subject hierarchy node
- 2.78.2 Create subject hierarchy item
- 2.78.3 Get subject hierarchy item
- 2.78.4 Traverse children of a subject hierarchy item
- 2.78.5 Manipulate subject hierarchy item
- 2.78.6 Filter items in TreeView or ComboBox
- 2.78.7 Listen to subject hierarchy item events
- 2.79 Plotting
- 2.80 Execute external applications
- 2.81 Manage extensions
Community-contributed modules
The examples in this section are Scripted Modules that provide a user interface in the module panel along with specialized implementation logic.
Usage: save the .py file to a directory, add the directory to the additional module paths in the Slicer application settings (choose in the menu: Edit / Application settings, click Modules, click >> next to Additional module paths, click Add, and choose the .py file's location).
More information about python scripted modules and more usage examples can be found in the Python scripting wiki page.
Filters
- 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 header browser to easily scroll through dicom files using dcmdump.
- SlicerRT batch processing to batch convert RT structure sets to labelmap NRRD files.
Informatics
- MarkupsInfo.py: Compute the total length between all the points of a markup list.
- LineProfile.py: Compute intensity profile in a volume along a line.
Community-contributed examples
Usage: Copy-paste the shown code lines or linked .py file contents into Python console in Slicer. Or save them to a file and run them using execfile.
Capture
- Capture the full Slicer screen and save it into a file
img = qt.QPixmap.grabWidget(slicer.util.mainWindow()).toImage() img.save('c:/tmp/test.png')
- Capture all the views save it into a file:
import ScreenCapture cap = ScreenCapture.ScreenCaptureLogic() cap.showViewControllers(False) cap.captureImageFromView(None,'c:/tmp/test.png') cap.showViewControllers(True)
- Capture a single view:
viewNodeID = 'vtkMRMLViewNode1' import ScreenCapture cap = ScreenCapture.ScreenCaptureLogic() view = cap.viewFromNode(slicer.mrmlScene.GetNodeByID(viewNodeID)) cap.captureImageFromView(view,'c:/tmp/test.png')
Common values for viewNodeID: vtkMRMLSliceNodeRed, vtkMRMLSliceNodeYellow, vtkMRMLSliceNodeGreen, vtkMRMLViewNode1, vtkMRMLViewNode2. The ScreenCapture module can also create video animations of rotating views, slice sweeps, etc.
- Capture a slice view sweep into a series of PNG files - for example, Red slice view, 30 images, from position -125.0 to 75.0, into c:/tmp folder, with name image_00001.png, image_00002.png, ...
import ScreenCapture ScreenCapture.ScreenCaptureLogic().captureSliceSweep(getNode('vtkMRMLSliceNodeRed'), -125.0, 75.0, 30, "c:/tmp", "image_%05d.png")
- Capture 3D view into PNG file with transparent background
renderWindow = slicer.app.layoutManager().threeDWidget(0).threeDView().renderWindow() renderWindow.SetAlphaBitPlanes(1) wti = vtk.vtkWindowToImageFilter() wti.SetInputBufferTypeToRGBA() wti.SetInput(renderWindow) writer = vtk.vtkPNGWriter() writer.SetFileName("c:/tmp/screenshot.png") writer.SetInputConnection(wti.GetOutputPort()) writer.Write()
Launching Slicer
- How to open an .mrb file with Slicer at the command line?
Slicer.exe --python-code "slicer.util.loadScene( 'f:/2013-08-23-Scene.mrb' )"
- How to run a script in the Slicer environment in batch mode (without showing any graphical user interface)?
Slicer.exe --python-code "doSomething; doSomethingElse; etc." --testing --no-splash --no-main-window
Load volume from file
When loading a volume from file, it is recommended to set returnNode=True to retrieve the loaded volume node.
loadedVolumeNode = slicer.util.loadVolume('c:/Users/abc/Documents/MRHead.nrrd')
- Get a MRML node in the scene based on the node name and call methods of that object. For the MRHead sample data:
vol=slicer.util.getNode('MR*') vol.GetImageData().GetDimensions()
Show volume rendering automatically when a volume is loaded
To show volume rendering of a volume automatically when it is loaded, add the lines below to your .slicerrc file.
@vtk.calldata_type(vtk.VTK_OBJECT) def onNodeAdded(caller, event, calldata): node = calldata if isinstance(node, slicer.vtkMRMLVolumeNode): # Call showVolumeRendering using a timer instead of calling it directly # to allow the volume loading to fully complete. qt.QTimer.singleShot(0, lambda: showVolumeRendering(node)) def showVolumeRendering(volumeNode): print("Show volume rendering of node "+volumeNode.GetName()) volRenLogic = slicer.modules.volumerendering.logic() displayNode = volRenLogic.CreateDefaultVolumeRenderingNodes(volumeNode) displayNode.SetVisibility(True) scalarRange = volumeNode.GetImageData().GetScalarRange() if scalarRange[1]-scalarRange[0] < 1500: # small dynamic range, probably MRI displayNode.GetVolumePropertyNode().Copy(volRenLogic.GetPresetByName('MR-Default')) else: # larger dynamic range, probably CT displayNode.GetVolumePropertyNode().Copy(volRenLogic.GetPresetByName('CT-Chest-Contrast-Enhanced')) slicer.mrmlScene.AddObserver(slicer.vtkMRMLScene.NodeAddedEvent, onNodeAdded)
Automatically load volumes that are copied into a folder
This example shows how to implement a simple background task by using a timer. The background task is to check for any new volume files in folder and if there is any then automatically load it.
There are more efficient methods for file system monitoring or exchanging image data in real-time (for example, using OpenIGTLink), the example below is just for demonstration purposes.
incomingVolumeFolder = "c:/tmp/incoming" incomingVolumesProcessed = [] def checkForNewVolumes(): # Check if there is a new file in the from os import listdir from os.path import isfile, join for f in listdir(incomingVolumeFolder): if f in incomingVolumesProcessed: # this is an incoming file, it was already there continue filePath = join(incomingVolumeFolder, f) if not isfile(filePath): # ignore directories continue logging.info("Loading new file: "+f) incomingVolumesProcessed.append(f) slicer.util.loadVolume(filePath) # Check again in 3000ms qt.QTimer.singleShot(3000, checkForNewVolumes) # Start monitoring checkForNewVolumes()
DICOM
How to access top level tags of DICOM images imported into Slicer? For example, to print the first patient's first study's first series' "0020,0032" field:
db=slicer.dicomDatabase patientList=db.patients() studyList=db.studiesForPatient(patientList[0]) seriesList=db.seriesForStudy(studyList[0]) fileList=db.filesForSeries(seriesList[0]) # Note, fileValue accesses the database of cached top level tags # (nested tags are not included) print(db.fileValue(fileList[0],'0020,0032'))
How to access DICOM tags nested in a sequence
db=slicer.dicomDatabase patientList=db.patients() studyList=db.studiesForPatient(patientList[0]) seriesList=db.seriesForStudy(studyList[0]) fileList=db.filesForSeries(seriesList[0]) # use pydicom to access the full header, which requires # re-reading the dataset instead of using the database cache import pydicom pydicom.dcmread(fileList[0]) ds.CTExposureSequence[0].ExposureModulationType
How to access tag of a volume loaded from DICOM? For example, get the patient position stored in a volume:
volumeName='2: ENT IMRT' n=slicer.util.getNode(volumeName) instUids=n.GetAttribute('DICOM.instanceUIDs').split() filename=slicer.dicomDatabase.fileForInstance(instUids[0]) print(slicer.dicomDatabase.fileValue(filename,'0018,5100'))
How to access tag of an item in the Subject Hierachy tree? For example, get the content time tag of a structure set:
rtStructName = '3: RTSTRUCT: PROS' rtStructNode = slicer.util.getNode(rtStructName) shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene) rtStructShItemID = shNode.GetItemByDataNode(rtStructNode) ctSliceInstanceUids = shNode.GetItemAttribute(rtStructShItemID, 'DICOM.ReferencedInstanceUIDs').split() filename = slicer.dicomDatabase.fileForInstance(ctSliceInstanceUids[0]) print(slicer.dicomDatabase.fileValue(filename,'0008,0033'))
How to get path and filename of a loaded DICOM volume?
def pathFromNode(node): storageNode=node.GetStorageNode() if storageNode is not None: # loaded via drag-drop filepath=storageNode.GetFullNameFromFileName() else: # loaded via DICOM browser instanceUIDs=node.GetAttribute('DICOM.instanceUIDs').split() filepath=slicer.dicomDatabase.fileForInstance(instUids[0]) return filepath # example: node=slicer.util.getNode('volume1') path=self.pathFromNode(node) print("DICOM path=%s" % path)
How can I convert DICOM to NRRD on the command line?
/Applications/Slicer-4.6.2.app/Contents/MacOS/Slicer --no-main-window --python-code "node=slicer.util.loadVolume('/tmp/series/im0.dcm'); slicer.util.saveNode(node, '/tmp/output.nrrd'); exit()"
The same can be done on windows by using the top level Slicer.exe. Be sure to use forward slashes in the pathnames within quotes on the command line.
Export a volume to DICOM file format
volumeNode = getNode('CTChest') outputFolder = "c:/tmp/dicom-output" # Create patient and study and put the volume under the study shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene) patientItemID = shNode.CreateSubjectItem(shNode.GetSceneItemID(), "test patient") studyItemID = shNode.CreateStudyItem(patientItemID, "test study") volumeShItemID = shNode.GetItemByDataNode(volumeNode) shNode.SetItemParent(volumeShItemID, studyItemID) import DICOMScalarVolumePlugin exporter = DICOMScalarVolumePlugin.DICOMScalarVolumePluginClass() exportables = exporter.examineForExport(volumeShItemID) for exp in exportables: exp.directory = outputFolder exporter.export(exportables)
Customize table columns in DICOM browser
# Get browser and database dicomBrowser = slicer.modules.dicom.widgetRepresentation().self().dicomBrowser dicomDatabase = dicomBrowser.database() # Need to go this way, do not use slicer.dicomDatabase for this # Change column order dicomDatabase.setWeightForField('Series', 'SeriesDescription', 7) dicomDatabase.setWeightForField('Studies', 'StudyDescription', 6) # Change column visibility dicomDatabase.setVisibilityForField('Patients', 'PatientsBirthDate', False) # Change column name dicomDatabase.setDisplayedNameForField('Series', 'DisplayedCount', 'Number of images') # Customize table manager in DICOM browser dicomTableManager = dicomBrowser.dicomTableManager() dicomTableManager.selectionMode = qt.QAbstractItemView.SingleSelection dicomTableManager.autoSelectSeries = False
Toolbar functions
- How to turn on slice intersections in the crosshair menu on the toolbar:
viewNodes = slicer.util.getNodesByClass('vtkMRMLSliceCompositeNode') for viewNode in viewNodes: viewNode.SetSliceIntersectionVisibility(1)
How to find similar functions? For this one I searched for "slice intersections" text in the whole slicer source code, found that the function is implemented in Base\QTGUI\qSlicerViewersToolBar.cxx, then translated the qSlicerViewersToolBarPrivate::setSliceIntersectionVisible(bool visible) method to Python.
Manipulating objects in the slice viewer
- How to define/edit a circular region of interest in a slice viewer?
Drop two markup points on a slice view and copy-paste the code below into the Python console. After this, as you move the markups you’ll see a circle following the markups.
# Update the sphere from the fiducial points def UpdateSphere(param1, param2): import math centerPointCoord = [0.0, 0.0, 0.0] markups.GetNthFiducialPosition(0,centerPointCoord) circumferencePointCoord = [0.0, 0.0, 0.0] markups.GetNthFiducialPosition(1,circumferencePointCoord) sphere.SetCenter(centerPointCoord) radius=math.sqrt((centerPointCoord[0]-circumferencePointCoord[0])**2+(centerPointCoord[1]-circumferencePointCoord[1])**2+(centerPointCoord[2]-circumferencePointCoord[2])**2) sphere.SetRadius(radius) sphere.SetPhiResolution(30) sphere.SetThetaResolution(30) sphere.Update() # Get markup node from scene markups=slicer.util.getNode('F') sphere = vtk.vtkSphereSource() UpdateSphere(0,0) # Create model node and add to scene modelsLogic = slicer.modules.models.logic() model = modelsLogic.AddModel(sphere.GetOutput()) model.GetDisplayNode().SetSliceIntersectionVisibility(True) model.GetDisplayNode().SetSliceIntersectionThickness(3) model.GetDisplayNode().SetColor(1,1,0) # Call UpdateSphere whenever the fiducials are changed markups.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, UpdateSphere, 2)
Measure angle between two slice planes
Measure angle between red and yellow slice nodes. Whenever any of the slice nodes are moved, the updated angle is printed on the console.
sliceNodeIds = ['vtkMRMLSliceNodeRed', 'vtkMRMLSliceNodeYellow'] # Print angles between slice nodes def ShowAngle(unused1=None, unused2=None): sliceNormalVector = [] for sliceNodeId in sliceNodeIds: sliceToRAS = slicer.mrmlScene.GetNodeByID(sliceNodeId).GetSliceToRAS() sliceNormalVector.append([sliceToRAS.GetElement(0,2), sliceToRAS.GetElement(1,2), sliceToRAS.GetElement(2,2)]) angleRad = vtk.vtkMath.AngleBetweenVectors(sliceNormalVector[0], sliceNormalVector[1]) angleDeg = vtk.vtkMath.DegreesFromRadians(angleRad) print('Angle between slice planes = {0:0.3f}'.format(angleDeg)) # Observe slice node changes for sliceNodeId in sliceNodeIds: slicer.mrmlScene.GetNodeByID(sliceNodeId).AddObserver(vtk.vtkCommand.ModifiedEvent, ShowAngle) # Print current angle ShowAngle()
Set slice position and orientation from 3 markup fiducials
Drop 3 markup points in the scene and copy-paste the code below into the Python console. After this, as you move the markups you’ll see the red slice view position and orientation will be set to make it fit to the 3 points.
# Update plane from fiducial points def UpdateSlicePlane(param1=None, param2=None): # Get point positions as numpy array import numpy as np nOfFiduciallPoints = markups.GetNumberOfFiducials() if nOfFiduciallPoints < 3: return # not enough points points = np.zeros([3,nOfFiduciallPoints]) for i in range(0, nOfFiduciallPoints): markups.GetNthFiducialPosition(i, points[:,i]) # Compute plane position and normal planePosition = points.mean(axis=1) planeNormal = np.cross(points[:,1] - points[:,0], points[:,2] - points[:,0]) planeX = points[:,1] - points[:,0] sliceNode.SetSliceToRASByNTP(planeNormal[0], planeNormal[1], planeNormal[2], planeX[0], planeX[1], planeX[2], planePosition[0], planePosition[1], planePosition[2], 0) # Get markup node from scene sliceNode = slicer.app.layoutManager().sliceWidget('Red').mrmlSliceNode() markups = slicer.util.getNode('F') # Update slice plane manually UpdateSlicePlane() # Update slice plane automatically whenever points are changed markupObservation = [markups, markups.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, UpdateSlicePlane, 2)]
To stop automatic updates, run this:
markupObservation[0].RemoveObserver(markupObservation[1])
Set slice position and orientation from a normal vector and position
This code snippet shows how to display a slice view defined by a normal vector and position in an anatomically sensible way: rotating slice view so that "up" direction (or "right" direction) is towards an anatomical axis.
def setSlicePoseFromSliceNormalAndPosition(sliceNode, sliceNormal, slicePosition, defaultViewUpDirection=None, backupViewRightDirection=None): """ Set slice pose from the provided plane normal and position. View up direction is determined automatically, to make view up point towards defaultViewUpDirection. :param defaultViewUpDirection Slice view will be spinned in-plane to match point approximately this up direction. Default: patient superior. :param backupViewRightDirection Slice view will be spinned in-plane to match point approximately this right direction if defaultViewUpDirection is too similar to sliceNormal. Default: patient left. """ # Fix up input directions if defaultViewUpDirection is None: defaultViewUpDirection = [0,0,1] if backupViewRightDirection is None: backupViewRightDirection = [-1,0,0] if sliceNormal[1]>=0: sliceNormalStandardized = sliceNormal else: sliceNormalStandardized = [-sliceNormal[0], -sliceNormal[1], -sliceNormal[2]] # Compute slice axes sliceNormalViewUpAngle = vtk.vtkMath.AngleBetweenVectors(sliceNormalStandardized, defaultViewUpDirection) angleTooSmallThresholdRad = 0.25 # about 15 degrees if sliceNormalViewUpAngle > angleTooSmallThresholdRad and sliceNormalViewUpAngle < vtk.vtkMath.Pi() - angleTooSmallThresholdRad: viewUpDirection = defaultViewUpDirection sliceAxisY = viewUpDirection sliceAxisX = [0, 0, 0] vtk.vtkMath.Cross(sliceAxisY, sliceNormalStandardized, sliceAxisX) else: sliceAxisX = backupViewRightDirection # Set slice axes sliceNode.SetSliceToRASByNTP(sliceNormalStandardized[0], sliceNormalStandardized[1], sliceNormalStandardized[2], sliceAxisX[0], sliceAxisX[1], sliceAxisX[2], slicePosition[0], slicePosition[1], slicePosition[2], 0) # Example usage: sliceNode = getNode('vtkMRMLSliceNodeRed') transformNode = getNode('Transform_3') transformMatrix = vtk.vtkMatrix4x4() transformNode.GetMatrixTransformToParent(transformMatrix) sliceNormal = [transformMatrix.GetElement(0,2), transformMatrix.GetElement(1,2), transformMatrix.GetElement(2,2)] slicePosition = [transformMatrix.GetElement(0,3), transformMatrix.GetElement(1,3), transformMatrix.GetElement(2,3)] setSlicePoseFromSliceNormalAndPosition(sliceNode, sliceNormal, slicePosition)
Switching to markup fiducial placement mode
To activate a fiducial placement mode, both interaction mode has to be set and a fiducial node has to be selected:
interactionNode = slicer.app.applicationLogic().GetInteractionNode() selectionNode = slicer.app.applicationLogic().GetSelectionNode() selectionNode.SetReferenceActivePlaceNodeClassName("vtkMRMLMarkupsFiducialNode") fiducialNode = slicer.vtkMRMLMarkupsFiducialNode() slicer.mrmlScene.AddNode(fiducialNode) fiducialNode.CreateDefaultDisplayNodes() selectionNode.SetActivePlaceNodeID(fiducialNode.GetID()) interactionNode.SetCurrentInteractionMode(interactionNode.Place)
Alternatively, qSlicerMarkupsPlaceWidget widget can be used to initiate markup placement:
# Temporary markups node markupsNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsFiducialNode") def placementModeChanged(active): print("Placement: " +("active" if active else "inactive")) # You can inspect what is in the markups node here, delete the temporary markup node, etc. # Create and set up widget that contains a single "place markup" button. The widget can be placed in the module GUI. placeWidget = slicer.qSlicerMarkupsPlaceWidget() placeWidget.setMRMLScene(slicer.mrmlScene) placeWidget.setCurrentNode(markupsNode) placeWidget.buttonsVisible=False placeWidget.placeButton().show() placeWidget.connect('activeMarkupsFiducialPlaceModeChanged(bool)', placementModeChanged) placeWidget.show()
Change markup fiducial display properties
Display properties are stored in display node(s) associated with the fiducial node.
fiducialNode = getNode('F') fiducialDisplayNode = fiducialNode.GetDisplayNode() fiducialDisplayNode.SetVisibility(False) # Hide all points fiducialDisplayNode.SetVisibility(True) # Show all points fiducialDisplayNode.SetSelectedColor(1,1,0) # Set color to yellow fiducialDisplayNode.SetViewNodeIDs(["vtkMRMLSliceNodeRed", "vtkMRMLViewNode1"]) # Only show in red slice view and first 3D view
Get a notification if a markup point position is modified
Event management of Slicer-4.11 version is still subject to change. The example below shows how point manipulation can be observed now.
def onMarkupChanged(caller,event): markupsNode = caller sliceView = markupsNode.GetAttribute('Markups.MovingInSliceView') movingMarkupIndex = markupsNode.GetDisplayNode().GetActiveControlPoint() if movingMarkupIndex >= 0: pos = [0,0,0] markupsNode.GetNthFiducialPosition(movingMarkupIndex, pos) isPreview = markupsNode.GetNthControlPointPositionStatus(movingMarkupIndex) == slicer.vtkMRMLMarkupsNode.PositionPreview if isPreview: logging.info("Point {0} is previewed at {1} in slice view {2}".format(movingMarkupIndex, pos, sliceView)) else: logging.info("Point {0} was moved {1} in slice view {2}".format(movingMarkupIndex, pos, sliceView)) else: logging.info("Points modified: slice view = {0}".format(sliceView)) def onMarkupStartInteraction(caller, event): markupsNode = caller sliceView = markupsNode.GetAttribute('Markups.MovingInSliceView') movingMarkupIndex = markupsNode.GetDisplayNode().GetActiveControlPoint() logging.info("Start interaction: point ID = {0}, slice view = {1}".format(movingMarkupIndex, sliceView)) def onMarkupEndInteraction(caller, event): markupsNode = caller sliceView = markupsNode.GetAttribute('Markups.MovingInSliceView') movingMarkupIndex = markupsNode.GetDisplayNode().GetActiveControlPoint() logging.info("End interaction: point ID = {0}, slice view = {1}".format(movingMarkupIndex, sliceView)) markupsNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsFiducialNode") markupsNode.CreateDefaultDisplayNodes() markupsNode.AddFiducial(0,0,0) markupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, onMarkupChanged) markupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointStartInteractionEvent, onMarkupStartInteraction) markupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointEndInteractionEvent, onMarkupEndInteraction)
Get a notification if a transform is modified
def onTransformNodeModified(transformNode, unusedArg2=None, unusedArg3=None): transformMatrix = vtk.vtkMatrix4x4() transformNode.GetMatrixTransformToWorld(transformMatrix) print("Position: [{0}, {1}, {2}]".format(transformMatrix.GetElement(0,3), transformMatrix.GetElement(1,3), transformMatrix.GetElement(2,3))) transformNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLTransformNode") transformNode.AddObserver(slicer.vtkMRMLTransformNode.TransformModifiedEvent, onTransformNodeModified)
# Example actions to perform def action1(): print('Action1 on markup '+str(slicer.clickedMarkupIndex)) def action2(): print('Action2 on markup '+str(slicer.clickedMarkupIndex)) def action3(): print('Action3 on markup '+str(slicer.clickedMarkupIndex)) # Clicked markup index is saved here to let the action # know which markup needs to be manipulated. slicer.clickedMarkupIndex = -1 # Create a simple menu menu = qt.QMenu() a1 = qt.QAction("Test", slicer.util.mainWindow()) a1.connect('triggered()', action1) menu.addAction(a1) a2 = qt.QAction("Action", slicer.util.mainWindow()) a2.connect('triggered()', action1) menu.addAction(a2) a3 = qt.QAction("Here", slicer.util.mainWindow()) a3.connect('triggered()', action1) menu.addAction(a3) # Add observer to a markup fiducial list @vtk.calldata_type(vtk.VTK_INT) def markupClickedCallback(caller, eventId, callData): slicer.clickedMarkupIndex = callData print('Open menu on markup '+str(slicer.clickedMarkupIndex)) menu.move(qt.QCursor.pos()) menu.show() markupsNode = getNode('F') observerTag = markupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointClickedEvent, markupClickedCallback)
Write markup positions to JSON file
markupNode = getNode('F') outputFileName = 'c:/tmp/test.json' # Get markup positions data = [] for fidIndex in range(markupNode.GetNumberOfFiducials()): coords=[0,0,0] markupNode.GetNthFiducialPosition(fidIndex,coords) data.append({'label': markupNode.GetNthFiducialLabel(), 'position': coords}) import json with open(outputFileName, 'w') as outfile: json.dump(data, outfile)
Write annotation ROI to JSON file
roiNode = getNode('R') outputFileName = "c:/tmp/test.json" # Get annotation ROI data center = [0,0,0] radius = [0,0,0] roiNode.GetControlPointWorldCoordinates(0, center) roiNode.GetControlPointWorldCoordinates(1, radius) data = {'center': radius, 'radius': radius} # Write to json file import json with open(outputFileName, 'w') as outfile: json.dump(data, outfile)
Show a simple surface mesh as a model node
This example shows how to display a simple surface mesh (a box, created by a VTK source filter) as a model node.
# Create and set up polydata source box = vtk.vtkCubeSource() box.SetXLength(30) box.SetYLength(20) box.SetZLength(15) box.SetCenter(10,20,5) # Create a model node that displays output of the source boxNode = slicer.modules.models.logic().AddModel(box.GetOutputPort()) # Adjust display properties boxNode.GetDisplayNode().SetColor(1,0,0) boxNode.GetDisplayNode().SetOpacity(0.8)
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
# use dummy image data here e = vtk.vtkImageEllipsoidSource() scene = slicer.mrmlScene # Create model node model = slicer.vtkMRMLModelNode() model.SetScene(scene) model.SetName(scene.GenerateUniqueName("2DImageModel")) planeSource = vtk.vtkPlaneSource() model.SetAndObservePolyData(planeSource.GetOutput()) # Create display node modelDisplay = slicer.vtkMRMLModelDisplayNode() modelDisplay.SetColor(1,1,0) # yellow modelDisplay.SetBackfaceCulling(0) modelDisplay.SetScene(scene) scene.AddNode(modelDisplay) model.SetAndObserveDisplayNodeID(modelDisplay.GetID()) # Add to scene modelDisplay.SetAndObserveTextureImageData(e.GetOutput()) scene.AddNode(model) transform = slicer.vtkMRMLLinearTransformNode() scene.AddNode(transform) model.SetAndObserveTransformNodeID(transform.GetID()) vTransform = vtk.vtkTransform() vTransform.Scale(50,50,50) vTransform.RotateX(30) transform.SetAndObserveMatrixTransformToParent(vTransform.GetMatrix())
Get scalar values at surface of a model
The following script allows getting selected scalar value at a selected position of a model. Position can be selected by moving the mouse while holding down Shift key.
modelNode = getNode('sphere') modelPointValues = modelNode.GetPolyData().GetPointData().GetArray("Normals") markupsNode = slicer.mrmlScene.GetFirstNodeByName('F') if not markupsNode: markupsNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsFiducialNode","F") pointsLocator = vtk.vtkPointLocator() # could try using vtk.vtkStaticPointLocator() if need to optimize pointsLocator.SetDataSet(modelNode.GetPolyData()) pointsLocator.BuildLocator() def onMouseMoved(observer,eventid): ras=[0,0,0] crosshairNode.GetCursorPositionRAS(ras) if markupsNode.GetNumberOfFiducials() == 0: markupsNode.AddFiducial(*ras) else: markupsNode.SetNthFiducialPosition(0,*ras) closestPointId = pointsLocator.FindClosestPoint(ras) closestPointValue = modelPointValues.GetTuple(closestPointId) print("RAS = " + repr(ras) + " value = " + repr(closestPointValue)) crosshairNode=slicer.util.getNode('Crosshair') observationId = crosshairNode.AddObserver(slicer.vtkMRMLCrosshairNode.CursorPositionModifiedEvent, onMouseMoved) # To stop printing of values run this: # crosshairNode.RemoveObserver(observationId)
Select cells of a using markups fiducial points
The following script selects cells of a model node that are closest to positions of markups fiducial points.
# Get input nodes modelNode = slicer.util.getNode('Segment_1') # select cells in this model markupsNode = slicer.util.getNode('F') # points will be selected at positions specified by this markups fiducial node # Create scalar array that will store selection state cellScalars = modelNode.GetMesh().GetCellData() selectionArray = cellScalars.GetArray('selection') if not selectionArray: selectionArray = vtk.vtkIntArray() selectionArray.SetName('selection') selectionArray.SetNumberOfValues(modelNode.GetMesh().GetNumberOfCells()) selectionArray.Fill(0) cellScalars.AddArray(selectionArray) # Set up coloring by selection array modelNode.GetDisplayNode().SetActiveScalar("selection", vtk.vtkAssignAttribute.CELL_DATA) modelNode.GetDisplayNode().SetAndObserveColorNodeID("vtkMRMLColorTableNodeWarm1") modelNode.GetDisplayNode().SetScalarVisibility(True) # Initialize cell locator cell = vtk.vtkCellLocator() cell.SetDataSet(modelNode.GetMesh()) cell.BuildLocator() def onPointsModified(observer=None, eventid=None): global markupsNode, selectionArray selectionArray.Fill(0) # set all cells to non-selected by default markupPoints = slicer.util.arrayFromMarkupsControlPoints(markupsNode) closestPoint = [0.0, 0.0, 0.0] cellObj = vtk.vtkGenericCell() cellId = vtk.mutable(0) subId = vtk.mutable(0) dist2 = vtk.mutable(0.0) for markupPoint in markupPoints: cell.FindClosestPoint(markupPoint, closestPoint, cellObj, cellId, subId, dist2) closestCell = cellId.get() if closestCell >=0: selectionArray.SetValue(closestCell, 100) # set selected cell's scalar value to non-zero selectionArray.Modified() # Initial update onPointsModified() # Automatic update each time when a markup point is modified markupsNodeObserverTag = markupsNode.AddObserver(slicer.vtkMRMLMarkupsFiducialNode.PointModifiedEvent, onPointsModified) # To stop updating selection, run this: # markupsNode.RemoveObserver(markupsNodeObserverTag)
Export entire scene as VRML
Save all surface meshes displayed in the scene (models, markups, etc). Solid colors and coloring by scalar is preserved. Textures are not supported.
exporter = vtk.vtkVRMLExporter() exporter.SetRenderWindow(slicer.app.layoutManager().threeDWidget(0).threeDView().renderWindow()) exporter.SetFileName('C:/tmp/something.wrl') exporter.Write()
Export model to Blender, including color by scalar
modelNode = getNode("Model") plyFilePath = "c:/tmp/model.ply" modelDisplayNode = modelNode.GetDisplayNode() triangles = vtk.vtkTriangleFilter() triangles.SetInputConnection(modelDisplayNode.GetOutputPolyDataConnection()) plyWriter = vtk.vtkPLYWriter() plyWriter.SetInputConnection(triangles.GetOutputPort()) lut = vtk.vtkLookupTable() lut.DeepCopy(modelDisplayNode.GetColorNode().GetLookupTable()) lut.SetRange(modelDisplayNode.GetScalarRange()) plyWriter.SetLookupTable(lut) plyWriter.SetArrayName(modelDisplayNode.GetActiveScalarName()) plyWriter.SetFileName(plyFilePath) plyWriter.Write()
Export a tract (FiberBundle) to Blender, including color
Note: an interactive version of this script is now included in the SlicerDMRI extension (module code). After installing SlicerDMRI, go to Modules -> Diffusion -> Import and Export -> Export tractography to PLY (mesh).
The example below shows how to export a tractography "FiberBundleNode" to a PLY file:
lineDisplayNode = getNode("*LineDisplay*") plyFilePath = "/tmp/fibers.ply" tuber = vtk.vtkTubeFilter() tuber.SetInputData(lineDisplayNode.GetOutputPolyData()) tuber.Update() tubes = tuber.GetOutputDataObject(0) scalars = tubes.GetPointData().GetArray(0) scalars.SetName("scalars") triangles = vtk.vtkTriangleFilter() triangles.SetInputData(tubes) triangles.Update() colorNode = lineDisplayNode.GetColorNode() lookupTable = vtk.vtkLookupTable() lookupTable.DeepCopy(colorNode.GetLookupTable()) lookupTable.SetTableRange(0,1) plyWriter = vtk.vtkPLYWriter() plyWriter.SetInputData(triangles.GetOutput()) plyWriter.SetLookupTable(lookupTable) plyWriter.SetArrayName("scalars") plyWriter.SetFileName(plyFilePath) plyWriter.Write()
Iterate over tract (FiberBundle) streamline points
This example shows how to access the points in each line of a FiberBundle as a numpy array (view).
from vtk.util.numpy_support import vtk_to_numpy fb = getNode("FiberBundle_F") # <- fill in node ID here # get point data as 1d array points = slicer.util.arrayFromModelPoints(fb) # get line cell ids as 1d array line_ids = vtk_to_numpy(fb.GetPolyData().GetLines().GetData()) # VTK cell ids are stored as # [ N0 c0_id0 ... c0_id0 # N1 c1_id0 ... c1_idN1 ] # so we need to # - read point count for each line (cell) # - grab the ids in that range from `line_ids` array defined above # - index the `points` array by those ids cur_idx = 1 for _ in range(pd.GetLines().GetNumberOfCells()): # - read point count for this line (cell) count = lines[cur_idx - 1] # - grab the ids in that range from `lines` index_array = line_ids[ cur_idx : cur_idx + count] # update to the next range cur_idx += count + 1 # - index the point array by those ids line_points = points[index_array] # do work here
Clone a node
This example shows how to make a copy of any node that appears in Subject Hierarchy (in Data module).
# Get a node from SampleData that we will clone import SampleData nodeToClone = SampleData.SampleDataLogic().downloadMRHead() # Clone the node shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene) itemIDToClone = shNode.GetItemByDataNode(nodeToClone) clonedItemID = slicer.modules.subjecthierarchy.logic().CloneSubjectHierarchyItem(shNode, itemIDToClone) clonedNode = shNode.GetItemDataNode(clonedItemID)
Clone a volume
This example shows how to clone the MRHead sample volume, including its pixel data and display settings.
sourceVolumeNode = slicer.util.getNode('MRHead') volumesLogic = slicer.modules.volumes.logic() clonedVolumeNode = volumesLogic.CloneVolume(slicer.mrmlScene, sourceVolumeNode, 'Cloned volume')
Create a new volume
This example shows how to create a new empty volume.
nodeName = "MyNewVolume" imageSize = [512, 512, 512] voxelType=vtk.VTK_UNSIGNED_CHAR imageOrigin = [0.0, 0.0, 0.0] imageSpacing = [1.0, 1.0, 1.0] imageDirections = [[1,0,0], [0,1,0], [0,0,1]] fillVoxelValue = 0 # Create an empty image volume, filled with fillVoxelValue imageData = vtk.vtkImageData() imageData.SetDimensions(imageSize) imageData.AllocateScalars(voxelType, 1) imageData.GetPointData().GetScalars().Fill(fillVoxelValue) # Create volume node volumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode", nodeName) volumeNode.SetOrigin(imageOrigin) volumeNode.SetSpacing(imageSpacing) volumeNode.SetIJKToRASDirections(imageDirections) volumeNode.SetAndObserveImageData(imageData) volumeNode.CreateDefaultDisplayNodes() volumeNode.CreateDefaultStorageNode()
Get value of a volume at specific voxel coordinates
This example shows how to get voxel value of "volumeNode" at "ijk" volume voxel coordinates.
volumeNode = slicer.util.getNode('MRHead') ijk = [20,40,30] # volume voxel coordinates voxels = slicer.util.arrayFromVolume(volumeNode) # get voxels as a numpy array voxelValue = voxels[ijk[2], ijk[1], ijk[0]] # note that numpy array index order is kji (not ijk)
Modify voxels in a volume
Typically the fastest and simplest way of modifying voxels is by using numpy operators. Voxels can be retrieved in a numpy array using the `array` method and modified using standard numpy methods. For example, threshold a volume:
nodeName = 'MRHead' thresholdValue = 100 voxelArray = array(nodeName) # get voxels as numpy array voxelArray[voxelArray < thresholdValue] = 0 # modify voxel values getNode(nodeName).Modified() # at the end of all processing, notify Slicer that the image modification is completed
This example shows how to change voxels values of the MRHead sample volume. The values will be computed by function f(r,a,s,) = (r-10)*(r-10)+(a+15)*(a+15)+s*s.
volumeNode=slicer.util.getNode('MRHead') ijkToRas = vtk.vtkMatrix4x4() volumeNode.GetIJKToRASMatrix(ijkToRas) imageData=volumeNode.GetImageData() extent = imageData.GetExtent() for k in range(extent[4], extent[5]+1): for j in range(extent[2], extent[3]+1): for i in range(extent[0], extent[1]+1): position_Ijk=[i, j, k, 1] position_Ras=ijkToRas.MultiplyPoint(position_Ijk) r=position_Ras[0] a=position_Ras[1] s=position_Ras[2] functionValue=(r-10)*(r-10)+(a+15)*(a+15)+s*s imageData.SetScalarComponentFromDouble(i,j,k,0,functionValue) imageData.Modified()
Get volume voxel coordinates from markup fiducial RAS coordinates
This example shows how to get voxel coordinate of a volume corresponding to a markup fiducial point position.
# Inputs volumeNode = getNode('MRHead') markupsNode = getNode('F') markupsIndex = 0 # Get point coordinate in RAS point_Ras = [0, 0, 0, 1] markupsNode.GetNthFiducialWorldCoordinates(markupsIndex, point_Ras) # If volume node is transformed, apply that transform to get volume's RAS coordinates transformRasToVolumeRas = vtk.vtkGeneralTransform() slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(None, volumeNode.GetParentTransformNode(), transformRasToVolumeRas) point_VolumeRas = transformRasToVolumeRas.TransformPoint(point_Ras[0:3]) # Get voxel coordinates from physical coordinates volumeRasToIjk = vtk.vtkMatrix4x4() volumeNode.GetRASToIJKMatrix(volumeRasToIjk) point_Ijk = [0, 0, 0, 1] volumeRasToIjk.MultiplyPoint(np.append(point_VolumeRas,1.0), point_Ijk) point_Ijk = [ int(round(c)) for c in point_Ijk[0:3] ] # Print output print(point_Ijk)
Get markup fiducial RAS coordinates from volume voxel coordinates
This example shows how to get position of maximum intensity voxel of a volume (determined by numpy, in IJK coordinates) in RAS coordinates so that it can be marked with a markup fiducial.
# Inputs volumeNode = getNode('MRHead') markupsNode = getNode('F') # Get voxel position in IJK coordinate system import numpy as np volumeArray = slicer.util.arrayFromVolume(volumeNode) # Get position of highest voxel value point_Kji = np.where(volumeArray == volumeArray.max()) point_Ijk = [point_Kji[2][0], point_Kji[1][0], point_Kji[0][0]] # Get physical coordinates from voxel coordinates volumeIjkToRas = vtk.vtkMatrix4x4() volumeNode.GetIJKToRASMatrix(volumeIjkToRas) point_VolumeRas = [0, 0, 0, 1] volumeIjkToRas.MultiplyPoint(np.append(point_Ijk,1.0), point_VolumeRas) # If volume node is transformed, apply that transform to get volume's RAS coordinates transformVolumeRasToRas = vtk.vtkGeneralTransform() slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(volumeNode.GetParentTransformNode(), None, transformVolumeRasToRas) point_Ras = transformVolumeRasToRas.TransformPoint(point_VolumeRas[0:3]) # Add a markup at the computed position and print its coordinates markupsNode.AddFiducial(point_Ras[0], point_Ras[1], point_Ras[2], "max") print(point_Ras)
Get the values of all voxels for a label value
If you have a background image called ‘Volume’ and a mask called ‘Volume-label’ created with the Editor you could do something like this:
import numpy volume = array(‘Volume’) label = array(‘Volume-label’) points = numpy.where( label == 1 ) # or use another label number depending on what you segmented values = volume[points] # this will be a list of the label values values.mean() # should match the mean value of LabelStatistics calculation as a double-check numpy.savetxt(‘values.txt’, values)
Access values in a DTI tensor volume
This example shows how to access individual tensors at the voxel level.
First load your DWI volume and estimate tensors to produce a DTI volume called ‘Output DTI Volume’
Then open the python window: View->Python interactor
Use this command to access tensors through numpy:
tensors = array('Output DTI Volume')
Type the following code into the Python window to access all tensor components using vtk commands:
volumeNode=slicer.util.getNode('Output DTI Volume') imageData=volumeNode.GetImageData() tensors = imageData.GetPointData().GetTensors() extent = imageData.GetExtent() idx = 0 for k in range(extent[4], extent[5]+1): for j in range(extent[2], extent[3]+1): for i in range(extent[0], extent[1]+1): tensors.GetTuple9(idx) idx += 1
Change window/level (brightness/contrast) or colormap of a volume
This example shows how to change window/level of the MRHead sample volume.
volumeNode = getNode('MRHead') displayNode = volumeNode.GetDisplayNode() displayNode.AutoWindowLevelOff() displayNode.SetWindow(50) displayNode.SetLevel(100)
Change color mapping from grayscale to rainbow:
displayNode.SetAndObserveColorNodeID('vtkMRMLColorTableNodeRainbow')
Make mouse left-click and drag on the image adjust window/level
In older Slicer versions, by default, left-click and drag in a slice view adjusted window/level of the displayed image. Window/level adjustment is now a new mouse mode that can be activated by clicking on its toolbar button or running this code:
slicer.app.applicationLogic().GetInteractionNode().SetCurrentInteractionMode(slicer.vtkMRMLInteractionNode.AdjustWindowLevel)
Create custom color table
This example shows how to create a new color table, for example with inverted color range from the default Ocean color table.
invertedocean = slicer.vtkMRMLColorTableNode() invertedocean.SetTypeToUser() invertedocean.SetNumberOfColors(256) invertedocean.SetName("InvertedOcean") for i in range(0,255): invertedocean.SetColor(i, 0.0, 1 - (i+1e-16)/255.0, 1.0, 1.0) slicer.mrmlScene.AddNode(invertedocean)
Manipulate a Slice View
Change slice offset
Equivalent to moving the slider in slice view controller.
layoutManager = slicer.app.layoutManager() red = layoutManager.sliceWidget('Red') redLogic = red.sliceLogic() # Print current slice offset position print(redLogic.GetSliceOffset()) # Change slice position redLogic.SetSliceOffset(20)
Change slice orientation
Get 'Red' slice node and rotate around X and Y axes.
sliceNode = slicer.app.layoutManager().sliceWidget('Red').mrmlSliceNode() sliceToRas = sliceNode.GetSliceToRAS() transform=vtk.vtkTransform() transform.SetMatrix(SliceToRAS) transform.RotateX(20) transform.RotateY(15) sliceToRas.DeepCopy(transform.GetMatrix()) sliceNode.UpdateMatrices()
Show slice views in 3D window
Equivalent to clicking 'eye' icon in the slice view controller.
layoutManager = slicer.app.layoutManager() for sliceViewName in layoutManager.sliceViewNames(): controller = layoutManager.sliceWidget(sliceViewName).sliceController() controller.setSliceVisible(True)
Reset field of view to show background volume maximized
Equivalent to click small rectangle button ("Adjust the slice viewer's field of view...") in the slice view controller.
slicer.util.resetSliceViews()
Rotate slice views to volume plane
Aligns slice views to volume axes, shows original image acquisition planes in slice views.
volumeNode = slicer.util.getNode('MRHead') layoutManager = slicer.app.layoutManager() for sliceViewName in layoutManager.sliceViewNames(): layoutManager.sliceWidget(sliceViewName).mrmlSliceNode().RotateToVolumePlane(volumeNode)
Iterate over current visible slice views, and set foreground and background images
slicer.util.setSliceViewerLayers(background=mrVolume, foreground=ctVolume)
Internally, this method performs something like this:
layoutManager = slicer.app.layoutManager() for sliceViewName in layoutManager.sliceViewNames(): compositeNode = layoutManager.sliceWidget(sliceViewName).sliceLogic().GetSliceCompositeNode() # setup background volume compositeNode.SetBackgroundVolumeID(mrVolume.GetID()) # setup foreground volume compositeNode.SetForegroundVolumeID(ctVolume.GetID()) # change opacity compositeNode.SetForegroundOpacity(0.3)
Show a volume in slice views
Recommended:
volumeNode = slicer.util.getNode('YourVolumeNode') slicer.util.setSliceViewerLayers(background=volumeNode)
or
Show volume in all visible views where volume selection propagation is enabled:
volumeNode = slicer.util.getNode('YourVolumeNode') applicationLogic = slicer.app.applicationLogic() selectionNode = applicationLogic.GetSelectionNode() selectionNode.SetSecondaryVolumeID(volumeNode.GetID()) applicationLogic.PropagateForegroundVolumeSelection(0)
or
Show volume in selected views:
n = slicer.util.getNode('YourVolumeNode') for color in ['Red', 'Yellow', 'Green']: slicer.app.layoutManager().sliceWidget(color).sliceLogic().GetSliceCompositeNode().SetForegroundVolumeID(n.GetID())
Change opacity of foreground volume in slice views
slicer.util.setSliceViewerLayers(foregroundOpacity=0.4)
or
Change opacity in a selected view
lm = slicer.app.layoutManager() sliceLogic = lm.sliceWidget('Red').sliceLogic() compositeNode = sliceLogic.GetSliceCompositeNode() compositeNode.SetForegroundOpacity(0.4)
Fit slice plane to markup fiducials
sliceNode = slicer.mrmlScene.GetNodeByID("vtkMRMLSliceNodeRed") markupsNode = slicer.mrmlScene.GetFirstNodeByName("F") # Get markup point positions as numpy arrays import numpy as np p1 = np.array([0,0,0]) p2 = np.array([0,0,0]) p3 = np.array([0,0,0]) markupsNode.GetNthFiducialPosition(0, p1) markupsNode.GetNthFiducialPosition(1, p2) markupsNode.GetNthFiducialPosition(2, p3) # Get plane axis directions n = np.cross(p2-p1, p2-p3) # plane normal direction n = n/np.linalg.norm(n) t = np.cross([0, 0, 1], n) # plane transverse direction t = t/np.linalg.norm(t) # Set slice plane orientation and position sliceNode.SetSliceToRASByNTP(n[0], n[1], n[2], t[0], t[1], t[2], p1[0], p1[1], p1[2], 0)
Save a series of images from a Slice View
You can use ScreenCapture module to capture series of images. To do it programmatically, save the following into a file such as '/tmp/record.py' and then in the slicer python console type "execfile('/tmp/record.py')"
layoutName = 'Green' imagePathPattern = '/tmp/image-%03d.png' steps = 10 widget = slicer.app.layoutManager().sliceWidget(layoutName) view = widget.sliceView() logic = widget.sliceLogic() bounds = [0,]*6 logic.GetSliceBounds(bounds) for step in range(steps): offset = bounds[4] + step/(1.*steps) * (bounds[5]-bounds[4]) logic.SetSliceOffset(offset) view.forceRender() image = qt.QPixmap.grabWidget(view).toImage() image.save(imagePathPattern % step)
Rasterize a model and save it to a series of image files
This example shows how to generate a stack of image files from an STL file:
inputModelFile = "/some/input/folder/ProstateMeanShape.stl" outputDir = "/some/output/folder" outputVolumeLabelValue = 100 outputVolumeSpacingMm = [0.5, 0.5, 0.5] outputVolumeMarginMm = [10.0, 10.0, 10.0] # Read model inputModel = slicer.util.loadModel(inputModelFile) # Determine output volume geometry and create a corresponding reference volume import math import numpy as np bounds = np.zeros(6) inputModel.GetBounds(bounds) imageData = vtk.vtkImageData() imageSize = [ int((bounds[axis*2+1]-bounds[axis*2]+outputVolumeMarginMm[axis]*2.0)/outputVolumeSpacingMm[axis]) for axis in range(3) ] imageOrigin = [ bounds[axis*2]-outputVolumeMarginMm[axis] for axis in range(3) ] imageData.SetDimensions(imageSize) imageData.AllocateScalars(vtk.VTK_UNSIGNED_CHAR, 1) imageData.GetPointData().GetScalars().Fill(0) referenceVolumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode") referenceVolumeNode.SetOrigin(imageOrigin) referenceVolumeNode.SetSpacing(outputVolumeSpacingMm) referenceVolumeNode.SetAndObserveImageData(imageData) referenceVolumeNode.CreateDefaultDisplayNodes() # Convert model to labelmap seg = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSegmentationNode') slicer.modules.segmentations.logic().ImportModelToSegmentationNode(inputModel, seg) seg.CreateBinaryLabelmapRepresentation() outputLabelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode') slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(seg, outputLabelmapVolumeNode, referenceVolumeNode) outputLabelmapVolumeArray = (slicer.util.arrayFromVolume(outputLabelmapVolumeNode) * outputVolumeLabelValue).astype('int8') # Write labelmap volume to series of TIFF files pip_install("imageio") import imageio for i in range(len(outputLabelmapVolumeArray)): imageio.imwrite(f'{outputDir}/image_{i:03}.tiff', outputLabelmapVolumeArray[i])
Save the scene into a new directory
# Create a new directory where the scene will be saved into import time sceneSaveDirectory = slicer.app.temporaryPath + "/saved-scene-" + time.strftime("%Y%m%d-%H%M%S") if not os.access(sceneSaveDirectory, os.F_OK): os.makedirs(sceneSaveDirectory) # Save the scene if slicer.app.applicationLogic().SaveSceneToSlicerDataBundleDirectory(sceneSaveDirectory, None): logging.info("Scene saved to: {0}".format(sceneSaveDirectory)) else: logging.error("Scene saving failed")
Save the scene into a single MRB file
# Generate file name import time sceneSaveFilename = slicer.app.temporaryPath + "/saved-scene-" + time.strftime("%Y%m%d-%H%M%S") + ".mrb" # Save scene if slicer.util.saveScene(sceneSaveFilename): logging.info("Scene saved to: {0}".format(sceneSaveFilename)) else: logging.error("Scene saving failed")
Save a node to file
Save a transform node to file (should work with any other node type, if file extension is set to a supported one):
myNode = getNode("LinearTransform_3") myStorageNode = myNode.CreateDefaultStorageNode() myStorageNode.SetFileName("c:/tmp/something.tfm") myStorageNode.WriteData(myNode)
Center the 3D View on the Scene
layoutManager = slicer.app.layoutManager() threeDWidget = layoutManager.threeDWidget(0) threeDView = threeDWidget.threeDView() threeDView.resetFocalPoint()
Rotate the 3D View
layoutManager = slicer.app.layoutManager() threeDWidget = layoutManager.threeDWidget(0) threeDView = threeDWidget.threeDView() threeDView.yaw()
Display text in a 3D view or slice view
The easiest way to show information overlaid on a viewer is to use corner annotations.
view=slicer.app.layoutManager().threeDWidget(0).threeDView() # Set text to "Something" view.cornerAnnotation().SetText(vtk.vtkCornerAnnotation.UpperRight,"Something") # Set color to red view.cornerAnnotation().GetTextProperty().SetColor(1,0,0) # Update the view view.forceRender()
Hide slice view annotations (DataProbe)
# Disable slice annotations immediately slicer.modules.DataProbeInstance.infoWidget.sliceAnnotations.sliceViewAnnotationsEnabled=False slicer.modules.DataProbeInstance.infoWidget.sliceAnnotations.updateSliceViewFromGUI() # Disable slice annotations persistently (after Slicer restarts) settings = qt.QSettings() settings.setValue('DataProbe/sliceViewAnnotations.enabled', 0)
Turning off interpolation
You can turn off interpolation for newly loaded volumes with this script from Steve Pieper.
def NoInterpolate(caller,event): for node in slicer.util.getNodes('*').values(): if node.IsA('vtkMRMLScalarVolumeDisplayNode'): node.SetInterpolate(0) slicer.mrmlScene.AddObserver(slicer.mrmlScene.NodeAddedEvent, NoInterpolate)
The below link explains how to put this in your startup script.
http://www.na-mic.org/Wiki/index.php/AHM2012-Slicer-Python#Refining_the_code_and_UI_with_slicerrc
Customize viewer layout
Show a custom layout of a 3D view on top of the red slice view:
customLayout = """ <layout type="vertical" split="true"> <item> <view class="vtkMRMLViewNode" singletontag="1"> <property name="viewlabel" action="default">1</property> </view> </item> <item> <view class="vtkMRMLSliceNode" singletontag="Red"> <property name="orientation" action="default">Axial</property> <property name="viewlabel" action="default">R</property> <property name="viewcolor" action="default">#F34A33</property> </view> </item> </layout> """ # Built-in layout IDs are all below 100, so you can choose any large random number # for your custom layout ID. customLayoutId=501 layoutManager = slicer.app.layoutManager() layoutManager.layoutLogic().GetLayoutNode().AddLayoutDescription(customLayoutId, customLayout) # Switch to the new custom layout layoutManager.setLayout(customLayoutId)
See description of standard layouts (that can be used as examples) here: https://github.com/Slicer/Slicer/blob/master/Libs/MRML/Logic/vtkMRMLLayoutLogic.cxx
You can use this code snippet to add a button to the layout selector toolbar:
# Add button to layout selector toolbar for this custom layout viewToolBar = mainWindow().findChild('QToolBar', 'ViewToolBar') layoutMenu = viewToolBar.widgetForAction(viewToolBar.actions()[0]).menu() layoutSwitchActionParent = layoutMenu # use `layoutMenu` to add inside layout list, use `viewToolBar` to add next the standard layout list layoutSwitchAction = layoutSwitchActionParent.addAction("My view") # add inside layout list layoutSwitchAction.setData(layoutId) layoutSwitchAction.setIcon(qt.QIcon(':Icons/Go.png')) layoutSwitchAction.setToolTip('3D and slice view') layoutSwitchAction.connect('triggered()', lambda layoutId = customLayoutId: slicer.app.layoutManager().setLayout(layoutId))
Customize keyboard shortcuts
Keyboard shortcuts can be specified for activating any Slicer feature by adding a couple of lines to your .slicerrc file.
For example, this script registers Ctrl+b, Ctrl+n, Ctrl+m, Ctrl+, keyboard shortcuts to switch between red, yellow, green, and 4-up view layouts.
shortcuts = [ ('Ctrl+b', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpRedSliceView)), ('Ctrl+n', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpYellowSliceView)), ('Ctrl+m', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpGreenSliceView)), ('Ctrl+,', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutFourUpView)) ] for (shortcutKey, callback) in shortcuts: shortcut = qt.QShortcut(slicer.util.mainWindow()) shortcut.setKey(qt.QKeySequence(shortcutKey)) shortcut.connect( 'activated()', callback)
Disable certain user interactions in slice views
For example, disable slice browsing using mouse wheel and keyboard shortcuts in the red slice viewer:
interactorStyle = slicer.app.layoutManager().sliceWidget('Red').sliceView().sliceViewInteractorStyle() interactorStyle.SetActionEnabled(interactorStyle.BrowseSlice, False)
Hide all slice view controllers:
lm = slicer.app.layoutManager() for sliceViewName in lm.sliceViewNames(): lm.sliceWidget(sliceViewName).sliceController().setVisible(False)
Hide all 3D view controllers:
lm = slicer.app.layoutManager() for viewIndex in range(slicer.app.layoutManager().threeDViewCount): lm.threeDWidget(0).threeDController().setVisible(False)
Change default slice view orientation
You can left-right "flip" slice view orientation presets (show patient left side on left/right side of the screen) by copy-pasting the script below to your .slicerrc.py file.
# Axial slice axes: # 1 0 0 # 0 1 0 # 0 0 1 axialSliceToRas=vtk.vtkMatrix3x3() # Coronal slice axes: # 1 0 0 # 0 0 -1 # 0 1 0 coronalSliceToRas=vtk.vtkMatrix3x3() coronalSliceToRas.SetElement(1,1, 0) coronalSliceToRas.SetElement(1,2, -1) coronalSliceToRas.SetElement(2,1, 1) coronalSliceToRas.SetElement(2,2, 0) # Replace orientation presets in all existing slice nodes and in the default slice node sliceNodes = slicer.util.getNodesByClass('vtkMRMLSliceNode') sliceNodes.append(slicer.mrmlScene.GetDefaultNodeByClass('vtkMRMLSliceNode')) for sliceNode in sliceNodes: orientationPresetName = sliceNode.GetOrientation() sliceNode.RemoveSliceOrientationPreset("Axial") sliceNode.AddSliceOrientationPreset("Axial", axialSliceToRas) sliceNode.RemoveSliceOrientationPreset("Coronal") sliceNode.AddSliceOrientationPreset("Coronal", coronalSliceToRas) sliceNode.SetOrientation(orientationPresetName)
Set all slice views linked by default
You can make slice views linked by default (when application starts or the scene is cleared) by copy-pasting the script below to your .slicerrc.py file.
# Set linked slice views in all existing slice composite nodes and in the default node sliceCompositeNodes = slicer.util.getNodesByClass('vtkMRMLSliceCompositeNode') defaultSliceCompositeNode = slicer.mrmlScene.GetDefaultNodeByClass('vtkMRMLSliceCompositeNode') if not defaultSliceCompositeNode: defaultSliceCompositeNode = slicer.mrmlScene.CreateNodeByClass('vtkMRMLSliceCompositeNode') slicer.mrmlScene.AddDefaultNode(defaultSliceCompositeNode) sliceCompositeNodes.append(defaultSliceCompositeNode) for sliceCompositeNode in sliceCompositeNodes: sliceCompositeNode.SetLinkedControl(True)
Set crosshair jump mode to centered by default
You can change default slice jump mode (when application starts or the scene is cleared) by copy-pasting the script below to your .slicerrc.py file.
crosshair=slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLCrosshairNode") crosshair.SetCrosshairBehavior(crosshair.CenteredJumpSlice)
Set up custom units in slice view ruler
For microscopy or micro-CT images you may want to switch unit to micrometer instead of the default mm. To do that, 1. change the unit in Application settings / Units and 2. update ruler display settings using the script below (it can be copied to your Application startup script):
lm = slicer.app.layoutManager() for sliceViewName in lm.sliceViewNames(): sliceView = lm.sliceWidget(sliceViewName).sliceView() displayableManagerCollection = vtk.vtkCollection() sliceView.getDisplayableManagers(displayableManagerCollection) for dmIndex in range(displayableManagerCollection.GetNumberOfItems()): displayableManager = displayableManagerCollection.GetItemAsObject(dmIndex) if not displayableManager.IsA("vtkMRMLRulerDisplayableManager"): continue displayableManager.RemoveAllRulerScalePresets() displayableManager.AddRulerScalePreset( 0.001, 5, 2, "nm", 1000.0) displayableManager.AddRulerScalePreset( 0.010, 5, 2, "nm", 1000.0) displayableManager.AddRulerScalePreset( 0.100, 5, 2, "nm", 1000.0) displayableManager.AddRulerScalePreset( 0.500, 5, 1, "nm", 1000.0) displayableManager.AddRulerScalePreset( 1.0, 5, 2, "um", 1.0) displayableManager.AddRulerScalePreset( 5.0, 5, 1, "um", 1.0) displayableManager.AddRulerScalePreset( 10.0, 5, 2, "um", 1.0) displayableManager.AddRulerScalePreset( 50.0, 5, 1, "um", 1.0) displayableManager.AddRulerScalePreset( 100.0, 5, 2, "um", 1.0) displayableManager.AddRulerScalePreset( 500.0, 5, 1, "um", 1.0) displayableManager.AddRulerScalePreset(1000.0, 5, 2, "mm", 0.001)
Show a slice view outside the view layout
layoutName = "TestSlice" layoutLabel = "TS" # ownerNode manages this view instead of the layout manager (it can be any node in the scene) viewOwnerNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScriptedModuleNode") # Create MRML nodes viewNode = slicer.vtkMRMLSliceNode() viewNode.SetName(layoutName) viewNode.SetLayoutName(layoutName) viewNode.SetLayoutLabel(layoutLabel) viewNode.SetLayoutColor(1, 1, 0) viewNode.SetAndObserveParentLayoutNodeID(viewOwnerNode.GetID()) viewNode = slicer.mrmlScene.AddNode(viewNode) sliceCompositeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSliceCompositeNode") sliceCompositeNode.SetLayoutName(layoutName) # Create widget viewWidget = slicer.qMRMLSliceWidget() viewWidget.sliceViewName = layoutName viewWidget.sliceViewLabel = layoutLabel c = viewNode.GetLayoutColor() viewWidget.sliceViewColor = qt.QColor.fromRgbF(c[0],c[1],c[2]) viewWidget.setMRMLScene(slicer.mrmlScene) viewWidget.setMRMLSliceNode(viewNode) viewWidget.show()
Show a 3D view outside the view layout
layoutName = "Test3DView" layoutLabel = "T3" # ownerNode manages this view instead of the layout manager (it can be any node in the scene) viewOwnerNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScriptedModuleNode") # Create MRML node viewNode = slicer.vtkMRMLViewNode() viewNode.SetName(layoutName) viewNode.SetLayoutName(layoutName) viewNode.SetLayoutLabel(layoutLabel) viewNode.SetLayoutColor(1, 1, 0) viewNode.SetAndObserveParentLayoutNodeID(viewOwnerNode.GetID()) viewNode = slicer.mrmlScene.AddNode(viewNode) # Create widget viewWidget = slicer.qMRMLThreeDWidget() viewWidget.viewLabel = layoutLabel viewWidget.viewColor = qt.QColor.fromRgbF(c[0],c[1],c[2]) viewWidget.setMRMLScene(slicer.mrmlScene) viewWidget.setMRMLViewNode(viewNode) viewWidget.show()
Get displayable manager of a certain type for a certain view
modelDisplayableManager = None threeDViewWidget = slicer.app.layoutManager().threeDWidget(0) managers = vtk.vtkCollection() threeDViewWidget.getDisplayableManagers(managers) for i in range(managers.GetNumberOfItems()): obj = managers.GetItemAsObject(i) if obj.IsA('vtkMRMLModelDisplayableManager'): modelDisplayableManager = obj break if modelDisplayableManager is None: logging.error('Failed to find the model displayable manager') return
Running an ITK filter in Python using SimpleITK
Open the "Sample Data" module and download "MR Head", then paste the following snippet in Python interactor:
import SampleData import SimpleITK as sitk import sitkUtils # Get input volume node inputVolumeNode = SampleData.SampleDataLogic().downloadMRHead() # Create new volume node for output outputVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLScalarVolumeNode', 'MRHeadFiltered') # Run processing inputImage = sitkUtils.PullVolumeFromSlicer(inputVolumeNode) filter = sitk.SignedMaurerDistanceMapImageFilter() outputImage = filter.Execute(inputImage) sitkUtils.PushVolumeToSlicer(outputImage, outputVolumeNode) # Show processing result slicer.util.setSliceViewerLayers(background=outputVolumeNode)
More information:
- See the SimpleITK documentation for SimpleITK examples: http://www.itk.org/SimpleITKDoxygen/html/examples.html
- sitkUtils in Slicer is used for pushing and pulling images from Slicer to SimpleITK: https://github.com/Slicer/Slicer/blob/master/Base/Python/sitkUtils.py
Get current mouse coordinates in a slice view
You can get 3D (RAS) coordinates of the current mouse cursor from the crosshair singleton node as shown in the example below:
def onMouseMoved(observer,eventid): ras=[0,0,0] crosshairNode.GetCursorPositionRAS(ras) print(ras) crosshairNode=slicer.util.getNode('Crosshair') crosshairNode.AddObserver(slicer.vtkMRMLCrosshairNode.CursorPositionModifiedEvent, onMouseMoved)
Get DataProbe text
You can get the mouse location in pixel coordinates along with the pixel value at the mouse by hitting the '.' (period) key in a slice view after pasting in the following code.
def printDataProbe(): infoWidget = slicer.modules.DataProbeInstance.infoWidget for layer in ('B', 'F', 'L'): print(infoWidget.layerNames[layer].text, infoWidget.layerIJKs[layer].text, infoWidget.layerValues[layer].text) s = qt.QShortcut(qt.QKeySequence('.'), mainWindow()) s.connect('activated()', printDataProbe)
Get reformatted image from a slice viewer as numpy array
Set up 'red' slice viewer to show thick slab reconstructed from 3 slices:
sliceNodeID = 'vtkMRMLSliceNodeRed' # Get image data from slice view sliceNode = slicer.mrmlScene.GetNodeByID(sliceNodeID) appLogic = slicer.app.applicationLogic() sliceLogic = appLogic.GetSliceLogic(sliceNode) sliceLayerLogic = sliceLogic.GetBackgroundLayer() reslice = sliceLayerLogic.GetReslice() reslicedImage = vtk.vtkImageData() reslicedImage.DeepCopy(reslice.GetOutput()) # Create new volume node using resliced image volumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode") volumeNode.SetIJKToRASMatrix(sliceNode.GetXYToRAS()) volumeNode.SetAndObserveImageData(reslicedImage) volumeNode.CreateDefaultDisplayNodes() volumeNode.CreateDefaultStorageNode() # Get voxels as a numpy array voxels = slicer.util.arrayFromVolume(volumeNode) print voxels.shape
Combine multiple volumes into one
This example combines two volumes into a new one by subtracting one from the other.
import SampleData [input1Volume, input2Volume] = SampleData.SampleDataLogic().downloadDentalSurgery() import slicer.util a = slicer.util.arrayFromVolume(input1Volume) b = slicer.util.arrayFromVolume(input2Volume) # 'a' and 'b' are numpy arrays, # they can be combined using any numpy array operations # to produce the result array 'c' c = b-a volumeNode = slicer.modules.volumes.logic().CloneVolume(input1Volume, "Difference") slicer.util.updateVolumeFromArray(volumeNode, c) setSliceViewerLayers(background=volumeNode)
Thick slab reconstruction and maximum/minimum intensity volume projections
Set up 'red' slice viewer to show thick slab reconstructed from 3 slices:
sliceNode = slicer.mrmlScene.GetNodeByID('vtkMRMLSliceNodeRed') appLogic = slicer.app.applicationLogic() sliceLogic = appLogic.GetSliceLogic(sliceNode) sliceLayerLogic = sliceLogic.GetBackgroundLayer() reslice = sliceLayerLogic.GetReslice() reslice.SetSlabModeToMean() reslice.SetSlabNumberOfSlices(10) # mean of 10 slices will computed reslice.SetSlabSliceSpacingFraction(0.3) # spacing between each slice is 0.3 pixel (total 10 * 0.3 = 3 pixel neighborhood) sliceNode.Modified()
Set up 'red' slice viewer to show maximum intensity projection (MIP):
sliceNode = slicer.mrmlScene.GetNodeByID('vtkMRMLSliceNodeRed') appLogic = slicer.app.applicationLogic() sliceLogic = appLogic.GetSliceLogic(sliceNode) sliceLayerLogic = sliceLogic.GetBackgroundLayer() reslice = sliceLayerLogic.GetReslice() reslice.SetSlabModeToMax() reslice.SetSlabNumberOfSlices(600) # use a large number of slices (600) to cover the entire volume reslice.SetSlabSliceSpacingFraction(0.5) # spacing between slices are 0.5 pixel (supersampling is useful to reduce interpolation artifacts) sliceNode.Modified()
The projected image is available in a vtkImageData object by calling reslice.GetOutput().
Change default file type for nodes (that have never been saved yet)
Default node can be specified that will be used as a basis of all new storage nodes. This can be used for setting default file extension. For example, change file format to STL for model nodes:
defaultModelStorageNode = slicer.vtkMRMLModelStorageNode() defaultModelStorageNode.SetDefaultWriteFileExtension('stl') slicer.mrmlScene.AddDefaultNode(defaultModelStorageNode)
To permanently change default file extension on your computer, copy-paste the code above into your application startup script (you can find its location in menu: Edit / Application settings / General / Application startup script).
Change file type for saving for all volumes (with already existing storage nodes)
If it is not necessary to preserve file paths then the simplest is to configure default storage node (as shown in the example above), then delete all existing storage nodes. When save dialog is opened, default storage nodes will be recreated.
# Delete existing model storage nodes so that they will be recreated with default settings existingModelStorageNodes = slicer.util.getNodesByClass('vtkMRMLModelStorageNode') for modelStorageNode in existingModelStorageNodes: slicer.mrmlScene.RemoveNode(modelStorageNode)
To update existing storage nodes to use new file extension (but keep all other parameters unchanged) you can use this approach (example is for volume storage):
requiredFileExtension = '.nia' originalFileExtension = '.nrrd' volumeNodes = slicer.util.getNodesByClass('vtkMRMLScalarVolumeNode') for volumeNode in volumeNodes: volumeStorageNode = volumeNode.GetStorageNode() if not volumeStorageNode: volumeNode.AddDefaultStorageNode() volumeStorageNode = volumeNode.GetStorageNode() volumeStorageNode.SetFileName(volumeNode.GetName()+requiredFileExtension) else: volumeStorageNode.SetFileName(volumeStorageNode.GetFileName().replace(originalFileExtension, requiredFileExtension))
To set all volume nodes to save uncompressed by default (add this to .slicerrc.py so it takes effect for the whole session):
#set the default volume storage to not compress by default defaultVolumeStorageNode = slicer.vtkMRMLVolumeArchetypeStorageNode() defaultVolumeStorageNode.SetUseCompression(0) slicer.mrmlScene.AddDefaultNode(defaultVolumeStorageNode) logging.info("Volume nodes will be stored uncompressed by default")
Same thing as above, but applied to all segmentations instead of volumes:
#set the default volume storage to not compress by default defaultVolumeStorageNode = slicer.vtkMRMLSegmentationStorageNode() defaultVolumeStorageNode.SetUseCompression(0) slicer.mrmlScene.AddDefaultNode(defaultVolumeStorageNode) logging.info("Segmentation nodes will be stored uncompressed
Sequences
Concatenate all sequences in the scene into a new sequence
# Get all sequence nodes in the scene sequenceNodes = slicer.util.getNodesByClass('vtkMRMLSequenceNode') mergedSequenceNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSequenceNode', 'Merged sequence') # Merge all sequence nodes into a new sequence node mergedIndexValue = 0 for sequenceNode in sequenceNodes: for itemIndex in range(sequenceNode.GetNumberOfDataNodes()): dataNode = sequenceNode.GetNthDataNode(itemIndex) mergedSequenceNode.SetDataNodeAtValue(dataNode, str(mergedIndexValue)) mergedIndexValue += 1 # Delete the sequence node we copied the data from, to prevent sharing of the same # node by multiple sequences slicer.mrmlScene.RemoveNode(sequenceNode) # Create a sequence browser node for the new merged sequence mergedSequenceBrowserNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSequenceBrowserNode', 'Merged') mergedSequenceBrowserNode.AddSynchronizedSequenceNode(mergedSequenceNode) slicer.modules.sequencebrowser.setToolBarActiveBrowserNode(mergedSequenceBrowserNode) # Show proxy node in slice viewers mergedProxyNode = mergedSequenceBrowserNode.GetProxyNode(mergedSequenceNode) slicer.util.setSliceViewerLayers(background=mergedProxyNode)
Segmentations
Create a segmentation from a labelmap volume and display in 3D
labelmapVolumeNode = getNode('label') seg = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSegmentationNode') slicer.modules.segmentations.logic().ImportLabelmapToSegmentationNode(labelmapVolumeNode, seg) seg.CreateClosedSurfaceRepresentation() slicer.mrmlScene.RemoveNode(labelmapVolumeNode)
The last line is optional. It removes the original labelmap volume so that the same information is not shown twice.
Export labelmap node from segmentation node
Export smallest possible labelmap:
seg = getNode('Segmentation') labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode') slicer.modules.segmentations.logic().ExportAllSegmentsToLabelmapNode(seg, labelmapVolumeNode)
Export labelmap that matches geometry of a chosen reference volume:
seg = getNode('Segmentation') labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode') slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, referenceVolumeNode)
Export by pressing Ctrl+Shift+s key:
outputPath = "c:/tmp" def exportLabelmap(): segmentationNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLSegmentationNode") referenceVolumeNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLScalarVolumeNode") labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode') slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, referenceVolumeNode) filepath = outputPath + "/" + referenceVolumeNode.GetName()+"-label.nrrd" slicer.util.saveNode(labelmapVolumeNode, filepath) slicer.mrmlScene.RemoveNode(labelmapVolumeNode.GetDisplayNode().GetColorNode()) slicer.mrmlScene.RemoveNode(labelmapVolumeNode) slicer.util.delayDisplay("Segmentation saved to "+filepath) shortcut = qt.QShortcut(slicer.util.mainWindow()) shortcut.setKey(qt.QKeySequence('Ctrl+Shift+s')) shortcut.connect( 'activated()', exportLabelmap)
Export model nodes from segmentation node
seg = getNode('Segmentation') exportedModelsNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLModelHierarchyNode') slicer.modules.segmentations.logic().ExportAllSegmentsToModelHierarchy(seg, exportedModelsNode)
Show a segmentation in 3D
Segmentation can only be shown in 3D if closed surface representation (or other 3D-displayable representation) is available. To create closed surface representation:
segmentation.CreateClosedSurfaceRepresentation()
Get a representation of a segment
Access binary labelmap stored in a segmentation node (without exporting it to a volume node) - if it does not exist, it will return None:
image = segmentationNode.GetBinaryLabelmapRepresentation(segmentID)
Get closed surface, if it does not exist, it will return None:
polydata = segmentationNode.GetClosedSurfaceRepresentation(segmentID)
Get binary labelmap representation. If it does not exist then it will be created for that single segment. Applies parent transforms by default (if not desired, another argument needs to be added to the end: false):
import vtkSegmentationCorePython as vtkSegmentationCore outputOrientedImageData = vtkSegmentationCore.vtkOrientedImageData() slicer.vtkSlicerSegmentationsModuleLogic.GetSegmentBinaryLabelmapRepresentation(segmentationNode, segmentID, outputOrientedImageData)
Same as above, for closed surface representation:
outputPolyData = vtk.vtkPolyData() slicer.vtkSlicerSegmentationsModuleLogic.GetSegmentClosedSurfaceRepresentation(segmentationNode, segmentID, outputPolyData)
Convert all segments using default path and conversion parameters
segmentationNode.CreateBinaryLabelmapRepresentation()
Convert all segments using custom path or conversion parameters
Change reference image geometry parameter based on an existing referenceImageData image:
import vtkSegmentationCorePython as vtkSegmentationCore referenceGeometry = vtkSegmentationCore.vtkSegmentationConverter.SerializeImageGeometry(referenceImageData) segmentation.SetConversionParameter(vtkSegmentationCore.vtkSegmentationConverter.GetReferenceImageGeometryParameterName(), referenceGeometry)
Re-convert using a modified conversion parameter
Changing smoothing factor for closed surface generation:
import vtkSegmentationCorePython as vtkSegmentationCore segmentation = getNode('Segmentation').GetSegmentation() # Turn of surface smoothing segmentation.SetConversionParameter('Smoothing factor','0.0') # Recreate representation using modified parameters (and default conversion path) segmentation.RemoveRepresentation(vtkSegmentationCore.vtkSegmentationConverter.GetSegmentationClosedSurfaceRepresentationName()) segmentation.CreateRepresentation(vtkSegmentationCore.vtkSegmentationConverter.GetSegmentationClosedSurfaceRepresentationName())
Get centroid of a segment in world (RAS) coordinates
This example shows how to get centroid of a segment in world coordinates and show that position in all slice views.
segmentationNode = getNode('Segmentation') segmentId = 'Segment_1' # Get array voxel coordinates import numpy as np seg=arrayFromSegment(segmentation_node, segmentId) # numpy array has voxel coordinates in reverse order (KJI instead of IJK) # and the array is cropped to minimum size in the segmentation mean_KjiCropped = [coords.mean() for coords in np.nonzero(seg)] # Get segmentation voxel coordinates segImage = segmentationNode.GetBinaryLabelmapRepresentation(segmentId) segImageExtent = segImage.GetExtent() # origin of the array in voxel coordinates is determined by the start extent mean_Ijk = [mean_KjiCropped[2], mean_KjiCropped[1], mean_KjiCropped[0]] + np.array([segImageExtent[0], segImageExtent[2], segImageExtent[4]]) # Get segmentation physical coordinates ijkToWorld = vtk.vtkMatrix4x4() segImage.GetImageToWorldMatrix(ijkToWorld) mean_World = [0, 0, 0, 1] ijkToRas.MultiplyPoint(np.append(mean_Ijk,1.0), mean_World) mean_World = mean_World[0:3] # If segmentation node is transformed, apply that transform to get RAS coordinates transformWorldToRas = vtk.vtkGeneralTransform() slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(segmentationNode.GetParentTransformNode(), None, transformWorldToRas) mean_Ras = transformWorldToRas.TransformPoint(mean_World) # Show mean position value and jump to it in all slice viewers print(mean_Ras) slicer.modules.markups.logic().JumpSlicesToLocation(mean_Ras[0], mean_Ras[1], mean_Ras[2], True)
How to run segment editor effects from a script
Editor effects are complex because they need to handle changing master volumes, undo/redo, masking operations, etc. Therefore, instead of using a segment editor effect, it is simpler to run the underlying filters directly from script.
This example demonstrates how to use Segment editor effects (without GUI, using qMRMLSegmentEditorWidget):
- brain tumor segmentation using grow from seeds effect
- skin surface extraction using thresholding and smoothing
- mask a volume with segments and compute histogram for each region
- create fat/muscle/bone segment by thresholding and report volume of each segment
This example shows how to perform operations on segmentations using VTK filters:
Accessing views, renderers, and cameras
Iterate through all 3D views in current layout:
layoutManager = slicer.app.layoutManager() for threeDViewIndex in range(layoutManager.threeDViewCount) : view = layoutManager.threeDWidget(threeDViewIndex).threeDView() threeDViewNode = view.mrmlViewNode() cameraNode = slicer.modules.cameras.logic().GetViewActiveCameraNode(threeDViewNode) print('View node for 3D widget ' + str(threeDViewIndex)) print(' Name: ' + threeDViewNode .GetName()) print(' ID: ' + threeDViewNode .GetID()) print(' Camera ID: ' + cameraNode.GetID())
Iterate through all slice views in current layout:
layoutManager = slicer.app.layoutManager() for sliceViewName in layoutManager.sliceViewNames(): view = layoutManager.sliceWidget(sliceViewName).sliceView() sliceNode = view.mrmlSliceNode() sliceLogic = slicer.app.applicationLogic().GetSliceLogic(sliceNode) compositeNode = sliceLogic.GetSliceCompositeNode() print('Slice view ' + str(sliceViewName)) print(' Name: ' + sliceNode.GetName()) print(' ID: ' + sliceNode.GetID()) print(' Background volume: {0}'.format(compositeNode.GetBackgroundVolumeID())) print(' Foreground volume: {0} (opacity: {1})'.format(compositeNode.GetForegroundVolumeID(), compositeNode.GetForegroundOpacity())) print(' Label volume: {0} (opacity: {1})'.format(compositeNode.GetLabelVolumeID(), compositeNode.GetLabelOpacity()))
For low-level manipulation of views, it is possible to access VTK render windows, renderers and cameras of views in the current layout.
renderWindow = view.renderWindow() renderers = renderWindow.GetRenderers() renderer = renderers.GetItemAsObject(0) camera = cameraNode.GetCamera()
Hide view controller bars
slicer.app.layoutManager().threeDWidget(0).threeDController().setVisible(False) slicer.app.layoutManager().sliceWidget('Red').sliceController().setVisible(False) slicer.app.layoutManager().plotWidget(0).plotController().setVisible(False) slicer.app.layoutManager().tableWidget(0).tableController().setVisible(False)
Customize widgets in view controller bars
sliceController = slicer.app.layoutManager().sliceWidget("Red").sliceController() # hide what is not needed sliceController.pinButton().hide() #sliceController.viewLabel().hide() sliceController.fitToWindowToolButton().hide() sliceController.sliceOffsetSlider().hide() # add custom widgets myButton = qt.QPushButton("My custom button") sliceController.barLayout().addWidget(b)
Change 3D view background color
renderWindow = slicer.app.layoutManager().threeDWidget(0).threeDView().renderWindow() renderer = renderWindow.GetRenderers().GetFirstRenderer() renderer.SetBackground(1,0,0) renderer.SetBackground2(1,0,0) renderWindow.Render()
Hide Slicer logo from main window (to increase space)
slicer.util.findChild(slicer.util.mainWindow(), 'LogoLabel').visible = False
Subject hierarchy
Get the pseudo-singleton subject hierarchy node
It manages the whole hierarchy and provides functions to access and manipulate
shNode = slicer.mrmlScene.GetSubjectHierarchyNode()
Create subject hierarchy item
# If it is for a data node, it is automatically created, but the create function can be used to set parent: shNode.CreateItem(parentItemID, dataNode) # If it is a hierarchy item without a data node, then the create function must be used: shNode.CreateSubjectItem(parentItemID, name) shNode.CreateFolderItem(parentItemID, name) shNode.CreateHierarchyItem(parentItemID, name, level) # Advanced method to set level attribute manually (usually subject, study, or folder, but it can be a virtual branch for example)
Get subject hierarchy item
Items in subject hierarchy are uniquely identified by integer IDs
# Get scene item ID first because it is the root item: sceneItemID = shNode.GetSceneItemID() # Get direct child by name subjectItemID = shNode.GetItemChildWithName(sceneItemID, 'Subject_1') # Get item for data node itemID = shNode.GetItemByDataNode(dataNode) # Get item by UID (such as DICOM) itemID = shNode.GetItemByUID(slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMUIDName(), seriesInstanceUid) itemID = shNode.GetItemByUIDList(slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMInstanceUIDName(), instanceUID) # Invalid item ID for checking validity of a given ID (most functions return the invalid ID when item is not found) invalidItemID = slicer.vtkMRMLSubjectHierarchyNode.GetInvalidItemID()
Traverse children of a subject hierarchy item
children = vtk.vtkIdList() shNode.GetItemChildren(parent, children) for i in range(children.GetNumberOfIds()): child = children.GetId(i) ...
Manipulate subject hierarchy item
Instead of node operations on the individual subject hierarchy nodes, item operations are performed on the one subject hierarchy node.
# Set item name shNode.SetItemName(itemID, 'NewName') # Set item parent (reparent) shNode.SetItemParent(itemID, newParentItemID) # Set visibility of data node associated to an item shNode.SetItemDisplayVisibility(itemID, 1) # Set visibility of whole branch # Note: Folder-type items (fodler, subject, study, etc.) create their own display nodes when show/hiding from UI. # The displayable managers use SH information to determine visibility of an item, so no need to show/hide individual leaf nodes any more. # Once the folder display node is created, it can be shown hidden simply using shNode.SetItemDisplayVisibility # From python, this is how to trigger creating a folder display node pluginHandler = slicer.qSlicerSubjectHierarchyPluginHandler().instance() folderPlugin = pluginHandler.pluginByName('Folder') folderPlugin.setDisplayVisibility(folderItemID, 1)
Filter items in TreeView or ComboBox
Displayed items can be filtered using setAttributeFilter method. An example of the usage can be found in the unit test. Modified version here:
print(shTreeView.displayedItemCount()) # 5 shTreeView.setAttributeFilter('DICOM.Modality') # Nodes must have this attribute print(shTreeView.displayedItemCount()) # 3 shTreeView.setAttributeFilter('DICOM.Modality','CT') # Have attribute and equal 'CT' print(shTreeView.displayedItemCount()) # 1 shTreeView.removeAttributeFilter() print(shTreeView.displayedItemCount()) # 5
Listen to subject hierarchy item events
The subject hierarchy node sends the node item id as calldata. Item IDs are vtkIdType, which are NOT vtkObjects. You need to use vtk.calldata_type(vtk.VTK_LONG) (otherwise the application crashes).
class MyListenerClass(VTKObservationMixin): def __init__(self): VTKObservationMixin.__init__(self) shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene) self.addObserver(shNode, shNode.SubjectHierarchyItemModifiedEvent, self.shItemModifiedEvent) @vtk.calldata_type(vtk.VTK_LONG) def shItemModifiedEvent(self, caller, eventId, callData): print("SH Node modified") print("SH item ID: {0}".format(callData))
Plotting
Slicer plots displayed in view layout
Create histogram plot of a volume and show it embedded in the view layout. More information: https://www.slicer.org/wiki/Documentation/Nightly/Developers/Plots
Using slicer.util.plot
utility function
# Get a volume from SampleData and compute its histogram import SampleData import numpy as np volumeNode = SampleData.SampleDataLogic().downloadMRHead() histogram = np.histogram(arrayFromVolume(volumeNode), bins=50) chartNode = slicer.util.plot(histogram, xColumnIndex = 1) chartNode.SetYAxisRangeAuto(False) chartNode.SetYAxisRange(0, 4e5)
Using MRML classes only
# Get a volume from SampleData import SampleData volumeNode = SampleData.SampleDataLogic().downloadMRHead() # Compute histogram values import numpy as np histogram = np.histogram(arrayFromVolume(volumeNode), bins=50) # Save results to a new table node tableNode=slicer.mrmlScene.AddNewNodeByClass("vtkMRMLTableNode") updateTableFromArray(tableNode, histogram) tableNode.GetTable().GetColumn(0).SetName("Count") tableNode.GetTable().GetColumn(1).SetName("Intensity") # Create plot plotSeriesNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLPlotSeriesNode", volumeNode.GetName() + ' histogram') plotSeriesNode.SetAndObserveTableNodeID(tableNode.GetID()) plotSeriesNode.SetXColumnName("Intensity") plotSeriesNode.SetYColumnName("Count") plotSeriesNode.SetPlotType(plotSeriesNode.PlotTypeScatterBar) plotSeriesNode.SetColor(0, 0.6, 1.0) # Create chart and add plot plotChartNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLPlotChartNode") plotChartNode.AddAndObservePlotSeriesNodeID(plotSeriesNode.GetID()) plotChartNode.YAxisRangeAutoOff() plotChartNode.SetYAxisRange(0, 500000) # Show plot in layout slicer.modules.plots.logic().ShowChartInLayout(plotChartNode)
Using matplotlib
Matplotlib may be used from within Slicer, but the default Tk backend locks up and crashes Slicer. However, Matplotlib may still be used through other backends. More details can be found on the MatPlotLib pages.
Non-interactive plot
try: import matplotlib except ModuleNotFoundError: pip_install('matplotlib') import matplotlib matplotlib.use('Agg') from pylab import * t1 = arange(0.0, 5.0, 0.1) t2 = arange(0.0, 5.0, 0.02) t3 = arange(0.0, 2.0, 0.01) subplot(211) plot(t1, cos(2*pi*t1)*exp(-t1), 'bo', t2, cos(2*pi*t2)*exp(-t2), 'k') grid(True) title('A tale of 2 subplots') ylabel('Damped') subplot(212) plot(t3, cos(2*pi*t3), 'r--') grid(True) xlabel('time (s)') ylabel('Undamped') savefig('MatplotlibExample.png') # Static image view pm = qt.QPixmap("MatplotlibExample.png") imageWidget = qt.QLabel() imageWidget.setPixmap(pm) imageWidget.setScaledContents(True) imageWidget.show()
Plot in Slicer Jupyter notebook
try: import matplotlib except ModuleNotFoundError: pip_install('matplotlib') import matplotlib matplotlib.use('Agg') from pylab import * t1 = arange(0.0, 5.0, 0.1) t2 = arange(0.0, 5.0, 0.02) t3 = arange(0.0, 2.0, 0.01) subplot(211) plot(t1, cos(2*pi*t1)*exp(-t1), 'bo', t2, cos(2*pi*t2)*exp(-t2), 'k') grid(True) title('A tale of 2 subplots') ylabel('Damped') subplot(212) plot(t3, cos(2*pi*t3), 'r--') grid(True) xlabel('time (s)') ylabel('Undamped') savefig('MatplotlibExample.png') display(filename='MatplotlibExample.png', type="image/png", binary=True)
Interactive plot using wxWidgets GUI toolkit
try: import matplotlib import wx except ModuleNotFoundError: pip_install('matplotlib wxPython') import matplotlib # Get a volume from SampleData and compute its histogram import SampleData import numpy as np volumeNode = SampleData.SampleDataLogic().downloadMRHead() histogram = np.histogram(arrayFromVolume(volumeNode), bins=50) # Set matplotlib to use WXAgg backend import matplotlib matplotlib.use('WXAgg') # Show an interactive plot import matplotlib.pyplot as plt fig, ax = plt.subplots() ax.plot(histogram[1][1:], histogram[0].astype(float)) ax.grid(True) ax.set_ylim((0, 4e5)) plt.show(block=False)
Execute external applications
How to run external applications from Slicer.
Run process in default environment
When a process is launched from Slicer then by default Slicer's ITK, VTK, Qt, etc. libraries are used. If an external application has its own version of these libraries, then the application is expected to crash. To prevent crashing, the application must be run in the environment where Slicer started up (without all Slicer-specific library paths). This startup environment can be retrieved using slicer.util.startupEnvironment().
Example: run Python3 script from Slicer:
command_to_execute = ["/usr/bin/python3", "-c", "print('hola')"] from subprocess import check_output check_output( command_to_execute, env=slicer.util.startupEnvironment() )
will output:
'hola\n'
On some systems, shell=True must be specified as well.
Manage extensions
Download and install extension
extensionName = 'SlicerIGT' em = slicer.app.extensionsManagerModel() if not em.isExtensionInstalled(extensionName): extensionMetaData = em.retrieveExtensionMetadataByName(extensionName) url = em.serverUrl().toString()+'/download/item/'+extensionMetaData['item_id'] extensionPackageFilename = slicer.app.temporaryPath+'/'+extensionMetaData['md5'] slicer.util.downloadFile(url, extensionPackageFilename) em.installExtension(extensionPackageFilename) slicer.util.restart()