Last active
November 5, 2020 08:38
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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