Commit 9ecca3f0 authored by finetjul's avatar finetjul

ENH: Add Scene Performance test

It measures time spent by some scene actions such as loading, closing,
restoring, relayouting a scene or modifying/adding nodes.
Issue #2642

Example of performance on a MacBookPro laptop (2.4GHz):
AddData (BrainAtlas2012.mrb) took 44865 msecs
CloseScene () took 28706 msecs
AddData (BrainAtlas2012.mrb) took 44579 msecs
ModifyNode (vtkMRMLScalarVolumeNode1) took 4 msecs
ModifyNode (vtkMRMLScalarVolumeNode2) took 35 msecs
ModifyNode (vtkMRMLScalarVolumeNode3) took 20 msecs
ModifyNode (vtkMRMLModelHierarchyNode2) took 14 msecs
ModifyNode (vtkMRMLModelNode4) took 0 msecs
ModifyNode (vtkMRMLModelDisplayNode5) took 14 msecs
ModifyNode (vtkMRMLModelHierarchyNode3) took 13 msecs
ModifyNode (vtkMRMLModelStorageNode1) took 4 msecs
ModifyNode (vtkMRMLModelHierarchyNode301) took 18 msecs
ModifyNode (vtkMRMLModelDisplayNode304) took 23 msecs
ModifyNode (vtkMRMLModelHierarchyNode51) took 19 msecs
AddNode (vtkMRMLModelNode302) took 46 msecs

git-svn-id: http://svn.slicer.org/Slicer4/trunk@21579 3bd1e089-480b-0410-8dfb-8563597acbee
parent fb51209d
......@@ -175,6 +175,7 @@ if(Slicer_USE_QtTesting AND Slicer_USE_PYTHONQT)
slicer_add_python_unittest(SCRIPT Slicer4Minute.py)
slicer_add_python_unittest(SCRIPT Charting.py)
slicer_add_python_unittest(SCRIPT SliceLinkLogic.py)
slicer_add_python_unittest(SCRIPT ScenePerformance.py)
slicer_add_python_unittest(SCRIPT RSNA2012Vis.py)
slicer_add_python_unittest(SCRIPT RSNA2012Quant.py)
slicer_add_python_unittest(SCRIPT slicerCloseCrashBug2590.py)
......@@ -192,6 +193,7 @@ if(Slicer_USE_QtTesting AND Slicer_USE_PYTHONQT)
Slicer4Minute.py
Charting.py
SliceLinkLogic.py
ScenePerformance.py
RSNA2012Vis.py
RSNA2012Quant.py
slicerCloseCrashBug2590.py
......@@ -200,6 +202,7 @@ if(Slicer_USE_QtTesting AND Slicer_USE_PYTHONQT)
)
set(KIT_PYTHON_RESOURCES
Resources/UI/ScenePerformance.ui
)
ctkMacroCompilePythonScript(
......
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ScenePerformance</class>
<widget class="qMRMLWidget" name="ScenePerformance">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>392</width>
<height>387</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Scene Performance</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Action</string>
</property>
<layout class="QFormLayout" name="formLayout">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::ExpandingFieldsGrow</enum>
</property>
<item row="1" column="0">
<widget class="QLabel" name="FileLabel">
<property name="text">
<string>File</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="ctkPathLineEdit" name="ActionPathLineEdit"/>
</item>
<item row="0" column="0" colspan="2">
<widget class="QComboBox" name="ActionComboBox">
<item>
<property name="text">
<string>Add Data (or Scene)</string>
</property>
</item>
<item>
<property name="text">
<string>Restore scene</string>
</property>
</item>
<item>
<property name="text">
<string>Close Scene</string>
</property>
</item>
<item>
<property name="text">
<string>Switch to Layout</string>
</property>
</item>
<item>
<property name="text">
<string>Add (copy of current) node</string>
</property>
</item>
<item>
<property name="text">
<string>Modify (current) node</string>
</property>
</item>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="URLLabel">
<property name="text">
<string>URL</string>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="ResultsLabel">
<property name="text">
<string>Results</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QTextEdit" name="ResultsTextEdit">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="LayoutLabel">
<property name="text">
<string>Layout</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QSpinBox" name="LayoutSpinBox">
<property name="minimum">
<number>2</number>
</property>
<property name="maximum">
<number>27</number>
</property>
</widget>
</item>
<item row="2" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="URLLineEdit"/>
</item>
<item>
<widget class="QLabel" name="URLFileNameLabel">
<property name="text">
<string>file name:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="URLFileNameLineEdit"/>
</item>
</layout>
</item>
<item row="5" column="0">
<widget class="QLabel" name="MRMLNodeLabel">
<property name="text">
<string>Node</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="qMRMLNodeComboBox" name="MRMLNodeComboBox">
<property name="showHidden">
<bool>true</bool>
</property>
<property name="addEnabled">
<bool>false</bool>
</property>
<property name="removeEnabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QPushButton" name="TimePushButton">
<property name="text">
<string>Run and time</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QSpinBox" name="RepeatSpinBox">
<property name="minimum">
<number>1</number>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="SceneViewLabel">
<property name="text">
<string>SceneView</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QSpinBox" name="SceneViewSpinBox"/>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>qMRMLNodeComboBox</class>
<extends>QWidget</extends>
<header>qMRMLNodeComboBox.h</header>
</customwidget>
<customwidget>
<class>qMRMLWidget</class>
<extends>QWidget</extends>
<header>qMRMLWidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ctkPathLineEdit</class>
<extends>QWidget</extends>
<header>ctkPathLineEdit.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>ScenePerformance</sender>
<signal>mrmlSceneChanged(vtkMRMLScene*)</signal>
<receiver>MRMLNodeComboBox</receiver>
<slot>setMRMLScene(vtkMRMLScene*)</slot>
<hints>
<hint type="sourcelabel">
<x>10</x>
<y>173</y>
</hint>
<hint type="destinationlabel">
<x>99</x>
<y>180</y>
</hint>
</hints>
</connection>
</connections>
</ui>
import os
import unittest
from __main__ import vtk, qt, ctk, slicer
#
# ScenePerformance
#
class ScenePerformance:
def __init__(self, parent):
parent.title = "Scene Performance"
parent.categories = ["Testing.TestCases"]
parent.dependencies = []
parent.contributors = ["Julien Finet (Kitware)"]
parent.helpText = """
This module was developed as a self test to perform the performance tests
"""
parent.acknowledgementText = """
This file was originally developed by Julien Finet, Kitware, Inc. and was partially funded by NIH grant 3P41RR013218-12S1.
"""
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['ScenePerformance'] = self.runTest
def runTest(self):
tester = ScenePerformanceTest()
tester.testAll()
#
# ScenePerformanceWidget
#
class ScenePerformanceWidget:
def __init__(self, parent = None):
if parent:
self.parent = parent
if not parent:
self.setup()
self.parent.show()
def setup(self):
loader = qt.QUiLoader()
moduleName = 'ScenePerformance' #scriptedModulesPath = '/Users/exxos/Work/Slicer/Slicer4/Slicer-Debug/Slicer-build/lib/Slicer-4.2/qt-scripted-modules'
scriptedModulesPath = eval('slicer.modules.%s.path' % moduleName.lower())
scriptedModulesPath = os.path.dirname(scriptedModulesPath)
path = os.path.join(scriptedModulesPath, 'Resources', 'UI', 'ScenePerformance.ui')
qfile = qt.QFile(path)
qfile.open(qt.QFile.ReadOnly)
widget = loader.load( qfile, self.parent )
self.layout = self.parent.layout()
self.layout.addWidget(widget)
self.reloadButton = qt.QPushButton("Reload")
self.reloadButton.toolTip = "Reload this module."
self.reloadButton.name = "ScenePerformance Reload"
self.layout.addWidget(self.reloadButton)
self.reloadButton.connect('clicked()', self.reloadModule)
self.runTestsButton = qt.QPushButton("Run tests")
self.runTestsButton.toolTip = "Run all the tests."
self.runTestsButton.name = "Run tests"
self.layout.addWidget(self.runTestsButton)
self.runTestsButton.connect('clicked()', self.runTests)
self.TimePushButton = self.findWidget(self.parent, 'TimePushButton')
self.ActionComboBox = self.findWidget(self.parent, 'ActionComboBox')
self.ActionPathLineEdit = self.findWidget(self.parent, 'ActionPathLineEdit')
self.ResultsTextEdit = self.findWidget(self.parent, 'ResultsTextEdit')
self.URLLineEdit = self.findWidget(self.parent, 'URLLineEdit')
self.URLFileNameLineEdit = self.findWidget(self.parent, 'URLFileNameLineEdit')
self.SceneViewSpinBox = self.findWidget(self.parent, 'SceneViewSpinBox')
self.LayoutSpinBox = self.findWidget(self.parent, 'LayoutSpinBox')
self.MRMLNodeComboBox = self.findWidget(self.parent, 'MRMLNodeComboBox')
self.RepeatSpinBox = self.findWidget(self.parent, 'RepeatSpinBox')
widget.setMRMLScene(slicer.mrmlScene)
#self.MRMLNodeComboBox.setMRMLScene(slicer.mrmlScene)
self.TimePushButton.connect('clicked()', self.timeAction)
self.ActionComboBox.connect('currentIndexChanged(int)', self.updateActionProperties)
self.updateActionProperties()
def timeAction(self):
tester = ScenePerformanceTest()
tester.setUp()
tester.setRepeat(self.RepeatSpinBox.value)
if self.ActionComboBox.currentIndex == 0: # Add Data
if (self.URLLineEdit.text == ''):
file = self.ActionPathLineEdit.currentPath
else:
logic = ScenePerformanceLogic()
file = logic.downloadFile(self.URLLineEdit.text, self.URLFileNameLineEdit.text)
results = tester.addData(file)
self.ResultsTextEdit.append(results)
elif self.ActionComboBox.currentIndex == 1: # Restore
results = tester.restoreSceneView(self.SceneViewSpinBox.value)
self.ResultsTextEdit.append(results)
elif self.ActionComboBox.currentIndex == 3: # Layout
results = tester.setLayout(self.LayoutSpinBox.value)
self.ResultsTextEdit.append(results)
elif self.ActionComboBox.currentIndex == 2: # Close
results = tester.closeScene()
self.ResultsTextEdit.append(results)
elif self.ActionComboBox.currentIndex == 4: # Add Node
node = self.MRMLNodeComboBox.currentNode()
results = tester.addNode(node)
self.ResultsTextEdit.append(results)
elif self.ActionComboBox.currentIndex == 5: # Modify Node
node = self.MRMLNodeComboBox.currentNode()
results = tester.modifyNode(node)
self.ResultsTextEdit.append(results)
def updateActionProperties(self):
enableAddData = True if self.ActionComboBox.currentIndex == 0 else False
self.ActionPathLineEdit.setEnabled(enableAddData)
self.URLLineEdit.setEnabled(enableAddData)
self.URLFileNameLineEdit.setEnabled(enableAddData)
self.SceneViewSpinBox.setEnabled(True if self.ActionComboBox.currentIndex == 1 else False)
self.LayoutSpinBox.setEnabled(True if self.ActionComboBox.currentIndex == 3 else False)
self.MRMLNodeComboBox.setEnabled(True if self.ActionComboBox.currentIndex == 4 or self.ActionComboBox.currentIndex == 5 else False)
def findWidget(self, widget, objectName):
if widget.objectName == objectName:
return widget
else:
children = []
for w in widget.children():
resulting_widget = self.findWidget(w, objectName)
if resulting_widget:
return resulting_widget
return None
def runTests(self):
tester = ScenePerformanceTest()
tester.testAll()
def reloadModule(self,moduleName="ScenePerformance"):
"""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()
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)
# create new widget inside existing parent
globals()[widgetName.lower()] = eval(
'globals()["%s"].%s(parent)' % (moduleName, widgetName))
globals()[widgetName.lower()].setup()
#
# ScenePerformanceLogic
#
class ScenePerformanceLogic:
def __init__(self):
pass
def hasImageData(self,volumeNode):
if not volumeNode:
print('no volume node')
return False
if volumeNode.GetImageData() == None:
print('no image data')
return False
return True
def downloadFile(self, downloadURL, downloadFileName):
downloads = (
(downloadURL, downloadFileName),
)
import urllib
for url,name in downloads:
filePath = slicer.app.temporaryPath + '/' + name
if not os.path.exists(filePath) or os.stat(filePath).st_size == 0:
urllib.urlretrieve(url, filePath)
return filePath
def startTiming(self):
self.Timer = qt.QTime()
self.Timer.start()
def stopTiming(self):
return self.Timer.elapsed()
class ScenePerformanceTest(unittest.TestCase):
def delayDisplay(self,message,msec=1000):
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):
self.Repeat = 1
self.delayDisplay("Setup")
#layoutManager = slicer.app.layoutManager()
#layoutManager.setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutConventionalView)
#slicer.mrmlScene.Clear(0)
def setRepeat(self, repeat):
self.Repeat = repeat
def runTest(self):
self.testAll()
def testAll(self):
self.setUp()
self.addURLData('http://slicer.kitware.com/midas3/download?items=10937', 'BrainAtlas2012.mrb')
self.modifyNodeByID('vtkMRMLScalarVolumeNode1')
self.modifyNodeByID('vtkMRMLScalarVolumeNode2')
self.modifyNodeByID('vtkMRMLScalarVolumeNode3')
self.modifyNodeByID('vtkMRMLScalarVolumeDisplayNode2')
self.modifyNodeByID('vtkMRMLModelHierarchyNode2')
self.modifyNodeByID('vtkMRMLModelNode4')
self.modifyNodeByID('vtkMRMLModelDisplayNode5')
self.modifyNodeByID('vtkMRMLModelHierarchyNode3')
self.modifyNodeByID('vtkMRMLModelStorageNode1')
self.addNodeByID('vtkMRMLModelNode302')
self.setLayout(3)
self.setLayout(2)
self.setLayout(4)
self.setLayout(5)
self.setLayout(6)
self.setLayout(7)
self.setLayout(8)
self.restoreSceneView(0)
self.restoreSceneView(0)
self.closeScene()
def reportPerformance(self, action, property, time):
message = self.displayPerformance(action, property, time)
print ( '<DartMeasurement name="%s-%s" type="numeric/integer">%s</DartMeasurement>' % (action, property, time))
return message
def displayPerformance(self, action, property, time):
message = '%s (%s) took %s msecs ' % (action, property, time)
self.delayDisplay(message)
return message
def addURLData(self, url, file):
logic = ScenePerformanceLogic()
file = logic.downloadFile(url, file)
self.addData(file)
def addData(self, file):
self.delayDisplay("Starting the AddData test")
logic = ScenePerformanceLogic()
averageTime = 0
for x in range(self.Repeat):
logic.startTiming()
ioManager = slicer.app.ioManager()
ioManager.loadFile(file)
time = logic.stopTiming()
self.displayPerformance('AddData', file, time)
averageTime = averageTime + time
averageTime = averageTime / self.Repeat
return self.reportPerformance('AddData', os.path.basename(file), averageTime)
def closeScene(self):
self.delayDisplay("Starting the Close Scene test")
logic = ScenePerformanceLogic()
averageTime = 0
for x in range(self.Repeat):
logic.startTiming()
slicer.mrmlScene.Clear(0)
time = logic.stopTiming()
self.displayPerformance('CloseScene', '', time)
averageTime = averageTime + time
averageTime = averageTime / self.Repeat
return self.reportPerformance('CloseScene', '', averageTime)
def restoreSceneView(self, sceneViewIndex):
node = slicer.mrmlScene.GetNthNodeByClass(sceneViewIndex, 'vtkMRMLSceneViewNode')
return self.restoreSceneViewNode(node)
def restoreSceneViewNode(self, node):
self.delayDisplay("Starting the Restore Scene test")
logic = ScenePerformanceLogic()
averageTime = 0
for x in range(self.Repeat):
logic.startTiming()
node.RestoreScene()
time = logic.stopTiming()
self.displayPerformance('RestoreSceneView', node.GetID(), time)
averageTime = averageTime + time
averageTime = averageTime / self.Repeat
return self.reportPerformance('RestoreSceneView', node.GetID(), averageTime)
def setLayout(self, layoutIndex):
self.delayDisplay("Starting the layout test")
logic = ScenePerformanceLogic()
averageTime = 0
for x in range(self.Repeat):
logic.startTiming()
layoutManager = slicer.app.layoutManager()
layoutManager.setLayout(layoutIndex)
time = logic.stopTiming()
self.displayPerformance('Layout', layoutIndex, time)
averageTime = averageTime + time
averageTime = averageTime / self.Repeat
return self.reportPerformance('Layout', layoutIndex, averageTime)
def addNodeByID(self, nodeID):
node = slicer.mrmlScene.GetNodeByID(nodeID)