Skip to content

Instantly share code, notes, and snippets.

@pieper
Last active November 5, 2020 08:38
Show Gist options
  • Save pieper/5770098 to your computer and use it in GitHub Desktop.
Save pieper/5770098 to your computer and use it in GitHub Desktop.
This is the final edited version of the VolumeScroller.py example script for this tutorial: http://www.na-mic.org/Wiki/index.php/2013_Project_Week_Breakout_Session:Slicer4Python
import os
import unittest
from __main__ import vtk, qt, ctk, slicer
#
# VolumeScroller
#
class VolumeScroller:
def __init__(self, parent):
parent.title = "VolumeScroller" # TODO make this more human readable by adding spaces
parent.categories = ["Examples"]
parent.dependencies = []
parent.contributors = ["Jean-Christophe Fillion-Robin (Kitware), Steve Pieper (Isomics)"] # replace with "Firstname Lastname (Org)"
parent.helpText = """
This is an example of scripted loadable module bundled in an extension.
"""
parent.acknowledgementText = """
This file was originally developed by Jean-Christophe Fillion-Robin, Kitware Inc. and Steve Pieper, Isomics, Inc. and was partially funded by NIH grant 3P41RR013218-12S1.
""" # replace with organization, grant and thanks.
self.parent = parent
# Add this test to the SelfTest module's list for discovery when the module
# is created. Since this module may be discovered before SelfTests itself,
# create the list if it doesn't already exist.
try:
slicer.selfTests
except AttributeError:
slicer.selfTests = {}
slicer.selfTests['VolumeScroller'] = self.runTest
def runTest(self):
tester = VolumeScrollerTest()
tester.runTest()
#
# qVolumeScrollerWidget
#
class VolumeScrollerWidget:
def __init__(self, parent = None):
if not parent:
self.parent = slicer.qMRMLWidget()
self.parent.setLayout(qt.QVBoxLayout())
self.parent.setMRMLScene(slicer.mrmlScene)
else:
self.parent = parent
self.layout = self.parent.layout()
if not parent:
self.setup()
self.parent.show()
def setup(self):
# Instantiate and connect widgets ...
#
# Reload and Test area
#
reloadCollapsibleButton = ctk.ctkCollapsibleButton()
reloadCollapsibleButton.text = "Reload && Test"
self.layout.addWidget(reloadCollapsibleButton)
reloadFormLayout = qt.QFormLayout(reloadCollapsibleButton)
# reload button
# (use this during development, but remove it when delivering
# your module to users)
self.reloadButton = qt.QPushButton("Reload")
self.reloadButton.toolTip = "Reload this module."
self.reloadButton.name = "VolumeScroller Reload"
reloadFormLayout.addWidget(self.reloadButton)
self.reloadButton.connect('clicked()', self.onReload)
# reload and test button
# (use this during development, but remove it when delivering
# your module to users)
self.reloadAndTestButton = qt.QPushButton("Reload and Test")
self.reloadAndTestButton.toolTip = "Reload this module and then run the self tests."
reloadFormLayout.addWidget(self.reloadAndTestButton)
self.reloadAndTestButton.connect('clicked()', self.onReloadAndTest)
#
# Volume Scrolling Area
#
scrollingCollapsibleButton = ctk.ctkCollapsibleButton()
scrollingCollapsibleButton.text = "Volume Scrolling"
self.layout.addWidget(scrollingCollapsibleButton)
# Layout within the scrolling collapsible button
scrollingFormLayout = qt.QFormLayout(scrollingCollapsibleButton)
# volume selection scroller
self.slider = ctk.ctkSliderWidget()
self.slider.decimals = 0
self.slider.enabled = False
scrollingFormLayout.addRow("Volume", self.slider)
# refresh button
self.refreshButton = qt.QPushButton("Refresh")
scrollingFormLayout.addRow(self.refreshButton)
# make connections
self.slider.connect('valueChanged(double)', self.onSliderValueChanged)
self.refreshButton.connect('clicked()', self.onRefresh)
# make an instance of the logic for use by the slots
self.logic = VolumeScrollerLogic()
# call refresh the slider to set it's initial state
self.onRefresh()
# Add vertical spacer
self.layout.addStretch(1)
def onSliderValueChanged(self,value):
self.logic.selectVolume(int(value))
def onRefresh(self):
volumeCount = self.logic.volumeCount()
self.slider.enabled = volumeCount > 0
self.slider.maximum = volumeCount-1
def cleanup(self):
pass
def onReload(self,moduleName="VolumeScroller"):
"""Generic reload method for any scripted module.
ModuleWizard will subsitute correct default moduleName.
"""
import imp, sys, os, slicer
widgetName = moduleName + "Widget"
# reload the source code
# - set source file path
# - load the module to the global space
filePath = eval('slicer.modules.%s.path' % moduleName.lower())
p = os.path.dirname(filePath)
if not sys.path.__contains__(p):
sys.path.insert(0,p)
fp = open(filePath, "r")
globals()[moduleName] = imp.load_module(
moduleName, fp, filePath, ('.py', 'r', imp.PY_SOURCE))
fp.close()
# rebuild the widget
# - find and hide the existing widget
# - create a new widget in the existing parent
parent = slicer.util.findChildren(name='%s Reload' % moduleName)[0].parent().parent()
for child in parent.children():
try:
child.hide()
except AttributeError:
pass
# Remove spacer items
item = parent.layout().itemAt(0)
while item:
parent.layout().removeItem(item)
item = parent.layout().itemAt(0)
# delete the old widget instance
if hasattr(globals()['slicer'].modules, widgetName):
getattr(globals()['slicer'].modules, widgetName).cleanup()
# create new widget inside existing parent
globals()[widgetName.lower()] = eval(
'globals()["%s"].%s(parent)' % (moduleName, widgetName))
globals()[widgetName.lower()].setup()
setattr(globals()['slicer'].modules, widgetName, globals()[widgetName.lower()])
def onReloadAndTest(self,moduleName="VolumeScroller"):
try:
self.onReload()
evalString = 'globals()["%s"].%sTest()' % (moduleName, moduleName)
tester = eval(evalString)
tester.runTest()
except Exception, e:
import traceback
traceback.print_exc()
qt.QMessageBox.warning(slicer.util.mainWindow(),
"Reload and Test", 'Exception!\n\n' + str(e) + "\n\nSee Python Console for Stack Trace")
#
# VolumeScrollerLogic
#
class VolumeScrollerLogic:
"""This class should implement all the actual
computation done by your module. The interface
should be such that other python code can import
this class and make use of the functionality without
requiring an instance of the Widget
"""
def __init__(self):
pass
def volumeCount(self):
return len(slicer.util.getNodes('vtkMRML*VolumeNode*'))
def selectVolume(self,index):
nodes = slicer.util.getNodes('vtkMRML*VolumeNode*')
names = nodes.keys()
names.sort()
selectionNode = slicer.app.applicationLogic().GetSelectionNode()
selectionNode.SetReferenceActiveVolumeID( nodes[names[index]].GetID() )
slicer.app.applicationLogic().PropagateVolumeSelection(0)
class VolumeScrollerTest(unittest.TestCase):
"""
This is the test case for your scripted module.
"""
def delayDisplay(self,message,msec=1000):
"""This utility method displays a small dialog and waits.
This does two things: 1) it lets the event loop catch up
to the state of the test so that rendering and widget updates
have all taken place before the test continues and 2) it
shows the user/developer/tester the state of the test
so that we'll know when it breaks.
"""
print(message)
self.info = qt.QDialog()
self.infoLayout = qt.QVBoxLayout()
self.info.setLayout(self.infoLayout)
self.label = qt.QLabel(message,self.info)
self.infoLayout.addWidget(self.label)
qt.QTimer.singleShot(msec, self.info.close)
self.info.exec_()
def setUp(self):
""" Do whatever is needed to reset the state - typically a scene clear will be enough.
"""
slicer.mrmlScene.Clear(0)
def runTest(self):
"""Run as few or as many tests as needed here.
"""
self.setUp()
self.test_VolumeScroller1()
def test_VolumeScroller1(self):
""" Ideally you should have several levels of tests. At the lowest level
tests sould exercise the functionality of the logic with different inputs
(both valid and invalid). At higher levels your tests should emulate the
way the user would interact with your code and confirm that it still works
the way you intended.
One of the most important features of the tests is that it should alert other
developers when their changes will have an impact on the behavior of your
module. For example, if a developer removes a feature that you depend on,
your test should break so they know that the feature is needed.
"""
self.delayDisplay("Starting the test")
#
# first, get some data
#
import urllib
downloads = (
('http://slicer.kitware.com/midas3/download?items=5767', 'FA.nrrd', slicer.util.loadVolume),
)
for url,name,loader in downloads:
filePath = slicer.app.temporaryPath + '/' + name
if not os.path.exists(filePath) or os.stat(filePath).st_size == 0:
print('Requesting download %s from %s...\n' % (name, url))
urllib.urlretrieve(url, filePath)
if loader:
print('Loading %s...\n' % (name,))
loader(filePath)
self.delayDisplay('Finished with download and loading\n')
volumeNode = slicer.util.getNode(pattern="FA")
logic = VolumeScrollerLogic()
volumesLogic = slicer.modules.volumes.logic()
blurLevelCount = 10
for sigma in range(blurLevelCount):
self.delayDisplay('Making blurred volume with sigma of %d\n' % sigma)
outputVolume = volumesLogic.CloneVolume(slicer.mrmlScene, volumeNode, 'blur-%d' % sigma)
parameters = {
"inputVolume": slicer.util.getNode('FA'),
"outputVolume": outputVolume,
"sigma": sigma,
}
blur = slicer.modules.gaussianblurimagefilter
slicer.cli.run(blur, None, parameters, wait_for_completion=True)
slicer.modules.VolumeScrollerWidget.onRefresh()
self.delayDisplay('Selecting original volume')
slicer.modules.VolumeScrollerWidget.slider.value = 0
self.delayDisplay('Selecting final volume')
slicer.modules.VolumeScrollerWidget.slider.value = blurLevelCount
selectionNode = slicer.app.applicationLogic().GetSelectionNode()
selectedID = selectionNode.GetActiveVolumeID()
lastVolumeID = outputVolume.GetID()
if selectedID != lastVolumeID:
raise Exception("Volume ID was not selected!\nExpected %s but got %s" % (lastVolumeID, selectedID))
self.delayDisplay('Test passed!')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment