Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python scripted interface #621

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Applications/SlicerApp/Testing/Python/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@ if(Slicer_USE_QtTesting AND Slicer_USE_PYTHONQT)
slicer_add_python_unittest(SCRIPT RSNAQuantTutorial.py)
slicer_add_python_unittest(SCRIPT slicerCloseCrashBug2590.py)
slicer_add_python_unittest(SCRIPT SlicerOrientationSelectorTest.py)
slicer_add_python_unittest(SCRIPT UtilTest.py)
slicer_add_python_unittest(SCRIPT ViewControllersSliceInterpolationBug1926.py)
slicer_add_python_unittest(SCRIPT RSNA2012ProstateDemo.py)
slicer_add_python_unittest(SCRIPT JRC2013Vis.py)
Expand Down Expand Up @@ -328,6 +329,7 @@ if(Slicer_USE_QtTesting AND Slicer_USE_PYTHONQT)
SubjectHierarchyCorePluginsSelfTest.py
TwoCLIsInARowTest.py
TwoCLIsInParallelTest.py
UtilTest.py
)

if(Slicer_BUILD_CLI_SUPPORT)
Expand All @@ -352,6 +354,7 @@ if(Slicer_USE_QtTesting AND Slicer_USE_PYTHONQT)

set(KIT_PYTHON_RESOURCES
Resources/UI/ScenePerformance.ui
Resources/UI/UtilTest.ui
)

ctkMacroCompilePythonScript(
Expand Down
25 changes: 25 additions & 0 deletions Applications/SlicerApp/Testing/Python/Resources/UI/UtilTest.ui
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>UtilTest</class>
<widget class="QWidget" name="UtilTest">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>304</width>
<height>31</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="UtilTest_Label">
<property name="text">
<string>My custom UI</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
187 changes: 187 additions & 0 deletions Applications/SlicerApp/Testing/Python/UtilTest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
import os
import unittest
from __main__ import vtk, qt, ctk, slicer
from slicer.ScriptedLoadableModule import *
import time

#
# UtilTest
#

class UtilTest(ScriptedLoadableModule):
def __init__(self, parent):
parent.title = "UtilTest" # TODO make this more human readable by adding spaces
parent.categories = ["Testing.TestCases"]
parent.dependencies = []
parent.contributors = ["Johan Andruejol (Kitware)"]
parent.helpText = """
This is a self test that tests the methods of slicer.util
"""
parent.acknowledgementText = """""" # 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['UtilTest'] = self.runTest

def runTest(self):
tester = UtilTestTest()
tester.runTest()

#
# UtilTestWidget
#

class UtilTestWidget(ScriptedLoadableModuleWidget):
def __init__(self):
ScriptedLoadableModuleWidget.__init__(self)
self.Widget = None

def setup(self):
ScriptedLoadableModuleWidget.setup(self)

moduleName = 'UtilTest'
scriptedModulesPath = os.path.dirname(slicer.util.modulePath(moduleName))
path = os.path.join(scriptedModulesPath, 'Resources', 'UI', moduleName + '.ui')

self.Widget = slicer.util.loadUI(path)
self.layout.addWidget(self.Widget)

#
# UtilTestLogic
#

class UtilTestLogic(ScriptedLoadableModuleLogic):
def __init__(self):
ScriptedLoadableModuleLogic.__init__(self)

#
# UtilTestLogic
#

class UtilTestTest(ScriptedLoadableModuleTest):

def setUp(self):
""" Reset the state for testing.
"""
slicer.mrmlScene.Clear(0)

def runTest(self):
"""Run as few or as many tests as needed here.
"""
self.setUp()
self.test_setSliceViewerLayers()
self.test_loadUI()
self.test_findChild()

def test_setSliceViewerLayers(self):
self.delayDisplay('Testing slicer.util.setSliceViewerLayers')

layoutManager = slicer.app.layoutManager()
layoutManager.layout = slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpRedSliceView

redSliceCompositeNode = slicer.mrmlScene.GetNodeByID('vtkMRMLSliceCompositeNodeRed')
self.assertIsNotNone(redSliceCompositeNode)
self.assertIsNone(redSliceCompositeNode.GetBackgroundVolumeID())
self.assertIsNone(redSliceCompositeNode.GetForegroundVolumeID())
self.assertIsNone(redSliceCompositeNode.GetLabelVolumeID())

backgroundNode = slicer.mrmlScene.AddNode(slicer.vtkMRMLScalarVolumeNode())
backgroundNode.SetName('Background')
foregroundNode = slicer.mrmlScene.AddNode(slicer.vtkMRMLScalarVolumeNode())
foregroundNode.SetName('Foreground')
labelmapNode = slicer.mrmlScene.AddNode(slicer.vtkMRMLScalarVolumeNode())
labelmapNode.SetName('Labelmap')

# Try with nothing
slicer.util.setSliceViewerLayers()
self.assertIsNone(redSliceCompositeNode.GetBackgroundVolumeID())
self.assertIsNone(redSliceCompositeNode.GetForegroundVolumeID())
self.assertIsNone(redSliceCompositeNode.GetLabelVolumeID())

# Try with nodes
slicer.util.setSliceViewerLayers(
background = backgroundNode,
foreground = foregroundNode,
label = labelmapNode,
foregroundOpacity = 0.5,
labelOpacity = 0.1
)
self.assertEqual(redSliceCompositeNode.GetBackgroundVolumeID(), backgroundNode.GetID())
self.assertEqual(redSliceCompositeNode.GetForegroundVolumeID(), foregroundNode.GetID())
self.assertEqual(redSliceCompositeNode.GetLabelVolumeID(), labelmapNode.GetID())
self.assertEqual(redSliceCompositeNode.GetForegroundOpacity(), 0.5)
self.assertEqual(redSliceCompositeNode.GetLabelOpacity(), 0.1)

# Try to reset
otherBackgroundNode = slicer.mrmlScene.AddNode(slicer.vtkMRMLScalarVolumeNode())
otherBackgroundNode.SetName('OtherBackground')
otherForegroundNode = slicer.mrmlScene.AddNode(slicer.vtkMRMLScalarVolumeNode())
otherForegroundNode.SetName('OtherForeground')
otherLabelmapNode = slicer.mrmlScene.AddNode(slicer.vtkMRMLScalarVolumeNode())
otherLabelmapNode.SetName('OtherLabelmap')

# Try with node id's
slicer.util.setSliceViewerLayers(
background = otherBackgroundNode.GetID(),
foreground = otherForegroundNode.GetID(),
label = otherLabelmapNode.GetID(),
foregroundOpacity = 0.0,
labelOpacity = 1.0
)
self.assertEqual(redSliceCompositeNode.GetBackgroundVolumeID(), otherBackgroundNode.GetID())
self.assertEqual(redSliceCompositeNode.GetForegroundVolumeID(), otherForegroundNode.GetID())
self.assertEqual(redSliceCompositeNode.GetLabelVolumeID(), otherLabelmapNode.GetID())
self.assertEqual(redSliceCompositeNode.GetForegroundOpacity(), 0.0)
self.assertEqual(redSliceCompositeNode.GetLabelOpacity(), 1.0)

self.delayDisplay('Testing slicer.util.setSliceViewerLayers passed !')

def test_loadUI(self):
# Try to load a UI that does not exist and catch exception
caughtException = False
try:
slicer.util.loadUI('does/not/exists.ui')
except RuntimeError:
caughtException = True
self.assertTrue(caughtException)

# Correct path
utilWidget = UtilTestWidget()
caughtException = False
try:
utilWidget.setup()
except RuntimeError:
caughtException = True
self.assertFalse(caughtException)

def test_findChild(self):
utilWidget = UtilTestWidget()

# Try with nothing (widget isn't setup)
caughtException = False
try:
slicer.util.findChild(utilWidget.Widget, 'UtilTest_Label')
except RuntimeError:
caughtException = True
self.assertTrue(caughtException)

utilWidget.setup()

# Try to get a widget that exists
label = slicer.util.findChild(utilWidget.Widget, 'UtilTest_Label')
self.assertIsNotNone(label, qt.QLabel)
self.assertEqual(label.text, 'My custom UI')

# Try to get a widget that does not exists
caughtException = False
try:
slicer.util.findChild(utilWidget.Widget, 'Unexistant_Label')
except RuntimeError:
caughtException = True
self.assertTrue(caughtException)
61 changes: 61 additions & 0 deletions Base/Python/slicer/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,67 @@ def findChildren(widget=None, name="", text="", title="", className=""):
children.append(p)
return children

def findChild(widget, name):
"""
Convenience method to access a widget by its ``name``.
A ``RuntimeError`` exception is raised if the widget with the
given ``name`` does not exist.
"""
errorMessage = "Widget named " + str(name) + " does not exists."
child = None
try:
child = findChildren(widget, name=name)[0]
if not child:
raise RuntimeError(errorMessage)
except IndexError:
raise RuntimeError(errorMessage)
return child

def loadUI(path):
""" Load UI file ``path`` and return the corresponding widget.
Raises a ``RuntimeError`` exception if the UI file is not found or if no
widget was instantiated.
"""
import qt
qfile = qt.QFile(path)
qfile.open(qt.QFile.ReadOnly)
loader = qt.QUiLoader()
widget = loader.load(qfile)
if not widget:
errorMessage = "Could not load UI file: " + str(path) + "\n\n"
raise RuntimeError(errorMessage)
return widget

def setSliceViewerLayers(background=None, foreground=None, label=None,
foregroundOpacity=None, labelOpacity=None):
""" Set the slice views with the given nodes.
:param background: node or node ID to be used for the background layer
:param foreground: node or node ID to be used for the foreground layer
:param label: node or node ID to be used for the label layer
:param foregroundOpacity: opacity of the foreground layer
:param labelOpacity: opacity of the label layer
"""
import slicer
def _nodeID(nodeOrID):
nodeID = nodeOrID
if isinstance(nodeOrID, slicer.vtkMRMLNode):
nodeID = nodeOrID.GetID()
return nodeID

num = slicer.mrmlScene.GetNumberOfNodesByClass('vtkMRMLSliceCompositeNode')
for i in range(num):
sliceViewer = slicer.mrmlScene.GetNthNodeByClass(i, 'vtkMRMLSliceCompositeNode')
if background is not None:
sliceViewer.SetBackgroundVolumeID(_nodeID(background))
if foreground is not None:
sliceViewer.SetForegroundVolumeID(_nodeID(foreground))
if foregroundOpacity is not None:
sliceViewer.SetForegroundOpacity(foregroundOpacity)
if label is not None:
sliceViewer.SetLabelVolumeID(_nodeID(label))
if labelOpacity is not None:
sliceViewer.SetLabelOpacity(labelOpacity)

#
# IO
#
Expand Down