An update will be applied December 9th, between 12PM and 1:00PM EST (UTC -5:00). The site may be slow during that time.

Commit 783cf816 authored by jcfr's avatar jcfr
Browse files

ENH: Speedup (from 2x to 10x faster) and simplify python tests


* Run python self tests with a messageDelay of 100ms. Message delay is used
  in function "delayDisplay" to (1) let the event loop catch up to the state
  of the test and (2) to display a meaningful message when running test
  interactively from Slicer.

  When running tests on the dashboard, there is no need to wait too long
  before each test steps. This commit sets a default of 50ms when tests
  are executed from the module panel. It also provides a Slicer to change
  the value.

* Refactor tests to use ScriptedLoadableModule base classes

* Remove redundant test case AbdominalAtlasTest already coverted by
  AtlasTests

* Remove duplicated implementation of functions like takeScreenshot.

git-svn-id: http://svn.slicer.org/Slicer4/trunk@27617 3bd1e089-480b-0410-8dfb-8563597acbee
parent 3dd50c90
import unittest
import slicer
import AtlasTests
class testClass():
""" Run the abdominal atlas test by itself
"""
def setUp(self):
print("Import the atlas tests...")
atlasTests = AtlasTests.AtlasTestsTest()
atlasTests.test_AbdominalAtlasTest()
class AbdominalAtlasTest(unittest.TestCase):
def setUp(self):
pass
def test_testClass(self):
test = testClass()
test.setUp()
import os
import unittest
import vtk, qt, ctk, slicer
from slicer.ScriptedLoadableModule import *
#
# AtlasTests
#
class AtlasTests:
class AtlasTests(ScriptedLoadableModule):
"""Uses ScriptedLoadableModule base class, available at:
https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
"""
def __init__(self, parent):
ScriptedLoadableModule.__init__(self, parent)
parent.title = "AtlasTests" # TODO make this more human readable by adding spaces
parent.categories = ["Testing.TestCases"]
parent.dependencies = []
......@@ -25,58 +31,20 @@ class AtlasTests:
parent.acknowledgementText = """
This file was originally developed by 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['AtlasTests'] = self.runTest
def runTest(self):
tester = AtlasTestsTest()
tester.runTest()
#
# qAtlasTestsWidget
#
class AtlasTestsWidget:
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()
class AtlasTestsWidget(ScriptedLoadableModuleWidget):
"""Uses ScriptedLoadableModuleWidget base class, available at:
https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
"""
def setup(self):
ScriptedLoadableModuleWidget.setup(self)
# Instantiate and connect widgets ...
# 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 = "AtlasTests Reload"
self.layout.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."
self.layout.addWidget(self.reloadAndTestButton)
self.reloadAndTestButton.connect('clicked()', self.onReloadAndTest)
# Collapsible button
atlasTests = ctk.ctkCollapsibleButton()
atlasTests.text = "Atlas Tests"
......@@ -118,31 +86,19 @@ class AtlasTestsWidget:
tester = AtlasTestsTest()
tester.runKneeTest()
def onReload(self,moduleName="AtlasTests"):
"""Generic reload method for any scripted module.
ModuleWizard will substitute correct default moduleName.
"""
globals()[moduleName] = slicer.util.reloadScriptedModule(moduleName)
def onReloadAndTest(self,moduleName="AtlasTests"):
self.onReload()
evalString = 'globals()["%s"].%sTest()' % (moduleName, moduleName)
tester = eval(evalString)
tester.runTest()
#
# AtlasTestsLogic
#
class AtlasTestsLogic:
class AtlasTestsLogic(ScriptedLoadableModuleLogic):
"""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
requiring an instance of the Widget.
Uses ScriptedLoadableModuleLogic base class, available at:
https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
"""
def __init__(self):
pass
def hasImageData(self,volumeNode):
"""This is a dummy logic method that
......@@ -158,28 +114,13 @@ class AtlasTestsLogic:
return True
class AtlasTestsTest(unittest.TestCase):
class AtlasTestsTest(ScriptedLoadableModuleTest):
"""
This is the test case for your scripted module.
Uses ScriptedLoadableModuleTest base class, available at:
https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
"""
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.
"""
......
......@@ -366,7 +366,6 @@ if(Slicer_USE_QtTesting AND Slicer_USE_PYTHONQT)
# add as unit test for use at build/test time
slicer_add_python_unittest(SCRIPT AtlasTests.py)
slicer_add_python_unittest(SCRIPT AbdominalAtlasTest.py)
slicer_add_python_unittest(SCRIPT DICOMReaders.py)
slicer_add_python_unittest(SCRIPT KneeAtlasTest.py)
slicer_add_python_unittest(SCRIPT sceneImport2428.py)
......
......@@ -5,13 +5,20 @@ import datetime
import time
import random
import vtk, qt, ctk, slicer
from slicer.ScriptedLoadableModule import *
#
# Charting
#
class Charting:
class Charting(ScriptedLoadableModule):
"""Uses ScriptedLoadableModule base class, available at:
https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
"""
def __init__(self, parent):
ScriptedLoadableModule.__init__(self, parent)
parent.title = "Charting" # TODO make this more human readable by adding spaces
parent.categories = ["Testing.TestCases"]
parent.dependencies = []
......@@ -22,58 +29,20 @@ class Charting:
parent.acknowledgementText = """
This file was originally developed by Jim Miller, GE and was partially funded by NIH grant U54EB005149.
""" # 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['Charting'] = self.runTest
def runTest(self):
tester = ChartingTest()
tester.runTest()
#
# qChartingWidget
#
class ChartingWidget:
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()
class ChartingWidget(ScriptedLoadableModuleWidget):
"""Uses ScriptedLoadableModuleWidget base class, available at:
https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
"""
def setup(self):
ScriptedLoadableModuleWidget.setup(self)
# Instantiate and connect widgets ...
# 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 = "Charting Reload"
self.layout.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."
self.layout.addWidget(self.reloadAndTestButton)
self.reloadAndTestButton.connect('clicked()', self.onReloadAndTest)
# Collapsible button
dummyCollapsibleButton = ctk.ctkCollapsibleButton()
dummyCollapsibleButton.text = "A collapsible button"
......@@ -97,68 +66,14 @@ class ChartingWidget:
def onHelloWorldButtonClicked(self):
print "Hello World !"
def onReload(self,moduleName="Charting"):
"""Generic reload method for any scripted module.
ModuleWizard will substitute correct default moduleName.
"""
globals()[moduleName] = slicer.util.reloadScriptedModule(moduleName)
def onReloadAndTest(self,moduleName="Charting"):
self.onReload()
evalString = 'globals()["%s"].%sTest()' % (moduleName, moduleName)
tester = eval(evalString)
tester.runTest()
#
# ChartingLogic
#
class ChartingLogic:
"""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 hasImageData(self,volumeNode):
"""This is a dummy logic method that
returns true if the passed in volume
node has valid image data
"""
if not volumeNode:
print('no volume node')
return False
if volumeNode.GetImageData() is None:
print('no image data')
return False
return True
class ChartingTest(unittest.TestCase):
class ChartingTest(ScriptedLoadableModuleTest):
"""
This is the test case for your scripted module.
Uses ScriptedLoadableModuleTest base class, available at:
https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
"""
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.
"""
......@@ -175,27 +90,6 @@ class ChartingTest(unittest.TestCase):
"""
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 = ChartingLogic()
#self.assertIsNotNone( logic.hasImageData(volumeNode) )
# Change the layout to one that has a chart. This created the ChartView
ln = slicer.mrmlScene.GetFirstNodeByClass('vtkMRMLLayoutNode')
......
......@@ -24,20 +24,6 @@ class DICOMReaders(ScriptedLoadableModule):
parent.acknowledgementText = """This work is supported primarily by the National Institutes of Health, National Cancer Institute, Informatics Technology for Cancer Research (ITCR) program, grant Quantitative Image Informatics for Cancer Research (QIICR) (U24 CA180918, PIs Kikinis and Fedorov). We also acknowledge support of the following grants: Neuroimage Analysis Center (NAC) (P41 EB015902, PI Kikinis) and National Center for Image Guided Therapy (NCIGT) (P41 EB015898, PI Tempany).
This file was originally developed by Steve Pieper, Isomics, Inc.
""" # 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['DICOMReaders'] = self.runTest
def runTest(self):
tester = DICOMReadersTest()
tester.runTest()
#
# qDICOMReadersWidget
......@@ -51,20 +37,6 @@ class DICOMReadersWidget(ScriptedLoadableModuleWidget):
self.layout.addStretch(1)
#
# DICOMReadersLogic
#
class DICOMReadersLogic(ScriptedLoadableModuleLogic):
"""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):
ScriptedLoadableModuleLogic.__init__(self)
class DICOMReadersTest(ScriptedLoadableModuleTest):
"""
......@@ -74,7 +46,7 @@ class DICOMReadersTest(ScriptedLoadableModuleTest):
def setUp(self):
""" Do whatever is needed to reset the state - typically a scene clear will be enough.
"""
self.delayDisplay("Closing the scene", 10)
self.delayDisplay("Closing the scene")
layoutManager = slicer.app.layoutManager()
layoutManager.setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutConventionalView)
slicer.mrmlScene.Clear(0)
......@@ -92,7 +64,7 @@ class DICOMReadersTest(ScriptedLoadableModuleTest):
"""
testPass = True
import os, json
self.delayDisplay("Starting the DICOM test", 100)
self.delayDisplay("Starting the DICOM test")
referenceData = json.JSONDecoder().decode('''[
{ "url": "http://slicer.kitware.com/midas3/download/item/292839/Mouse-MR-example-where-GDCM_fails.zip",
......@@ -126,16 +98,16 @@ class DICOMReadersTest(ScriptedLoadableModuleTest):
#
# first, get the data - a zip file of dicom data
#
self.delayDisplay("Downloading", 100)
self.delayDisplay("Downloading")
for dataset in referenceData:
try:
filePath = slicer.app.temporaryPath + '/' + dataset['fileName']
if not os.path.exists(filePath) or os.stat(filePath).st_size == 0:
self.delayDisplay('Requesting download %s from %s...\n' % (dataset['fileName'], dataset['url']), 100)
self.delayDisplay('Requesting download %s from %s...\n' % (dataset['fileName'], dataset['url']))
urllib.urlretrieve(dataset['url'], filePath)
self.delayDisplay('Finished with download\n', 100)
self.delayDisplay('Finished with download\n')
self.delayDisplay("Unzipping", 100)
self.delayDisplay("Unzipping")
dicomFilesDirectory = slicer.app.temporaryPath + dataset['name']
qt.QDir().mkpath(dicomFilesDirectory)
slicer.app.applicationLogic().Unzip(filePath, dicomFilesDirectory)
......@@ -143,10 +115,10 @@ class DICOMReadersTest(ScriptedLoadableModuleTest):
#
# insert the data into th database
#
self.delayDisplay("Switching to temp database directory", 100)
self.delayDisplay("Switching to temp database directory")
originalDatabaseDirectory = DICOMUtils.openTemporaryDatabase('tempDICOMDatabase')
self.delayDisplay('Importing DICOM', 100)
self.delayDisplay('Importing DICOM')
mainWindow = slicer.util.mainWindow()
mainWindow.moduleSelector().selectModule('DICOM')
......@@ -173,7 +145,7 @@ class DICOMReadersTest(ScriptedLoadableModuleTest):
basename = loadable.name
volumesByApproach = {}
for readerApproach in readerApproaches:
self.delayDisplay('Loading Selection with approach: %s' % readerApproach, 100)
self.delayDisplay('Loading Selection with approach: %s' % readerApproach)
loadable.name = basename + "-" + readerApproach
volumeNode = scalarVolumePlugin.load(loadable,readerApproach)
if not volumeNode and readerApproach not in dataset['expectedFailures']:
......@@ -212,15 +184,15 @@ class DICOMReadersTest(ScriptedLoadableModuleTest):
if len(failedComparisons.keys()) > 0:
raise Exception("Loaded volumes don't match: %s" % failedComparisons)
self.delayDisplay('%s Test passed!' % dataset['name'], 200)
self.delayDisplay('%s Test passed!' % dataset['name'])
except Exception, e:
import traceback
traceback.print_exc()
self.delayDisplay('%s Test caused exception!\n' % dataset['name'] + str(e), 2000)
self.delayDisplay('%s Test caused exception!\n' % dataset['name'] + str(e))
testPass = False
self.delayDisplay("Restoring original database directory", 200)
self.delayDisplay("Restoring original database directory")
mainWindow.moduleSelector().selectModule('DICOMReaders')
logging.info(loadingResult)
......@@ -237,7 +209,7 @@ reloadScriptedModule('DICOMReaders'); import DICOMReaders; tester = DICOMReaders
"""
testPass = True
import os, json
self.delayDisplay("Starting the DICOM test", 100)
self.delayDisplay("Starting the DICOM test")
datasetURL = "http://slicer.kitware.com/midas3/download/item/294857/deidentifiedMRHead-dcm-one-series.zip"
fileName = "deidentifiedMRHead-dcm-one-series.zip"
......@@ -270,11 +242,11 @@ reloadScriptedModule('DICOMReaders'); import DICOMReaders; tester = DICOMReaders
try:
if not os.path.exists(filePath) or os.stat(filePath).st_size == 0:
self.delayDisplay('Requesting download %s from %s...\n' % (fileName, datasetURL), 100)
self.delayDisplay('Requesting download %s from %s...\n' % (fileName, datasetURL))
urllib.urlretrieve(datasetURL, filePath)
self.delayDisplay('Finished with download\n', 100)
self.delayDisplay('Finished with download\n')
self.delayDisplay("Unzipping", 100)
self.delayDisplay("Unzipping")
dicomFilesDirectory = slicer.app.temporaryPath + 'MRhead'
qt.QDir().mkpath(dicomFilesDirectory)
slicer.app.applicationLogic().Unzip(filePath, dicomFilesDirectory)
......@@ -287,10 +259,10 @@ reloadScriptedModule('DICOMReaders'); import DICOMReaders; tester = DICOMReaders
#
# insert the data into the database
#
self.delayDisplay("Switching to temp database directory", 100)
self.delayDisplay("Switching to temp database directory")
originalDatabaseDirectory = DICOMUtils.openTemporaryDatabase('tempDICOMDatabase')
self.delayDisplay('Importing DICOM', 100)
self.delayDisplay('Importing DICOM')
mainWindow = slicer.util.mainWindow()
mainWindow.moduleSelector().selectModule('DICOM')
......@@ -320,15 +292,15 @@ reloadScriptedModule('DICOMReaders'); import DICOMReaders; tester = DICOMReaders
if not numpy.allclose(scalarVolumePlugin.acquisitionModeling.fixedCorners[-1], lastSliceCorners):
raise Exception("Acquisition transform didn't fix slice corners!")
self.delayDisplay('test_MissingSlices passed!', 200)
self.delayDisplay('test_MissingSlices passed!')
except Exception, e:
import traceback
traceback.print_exc()
self.delayDisplay('Missing Slices Test caused exception!\n' + str(e), 2000)
self.delayDisplay('Missing Slices Test caused exception!\n' + str(e))
testPass = False
self.delayDisplay("Restoring original database directory", 200)
self.delayDisplay("Restoring original database directory")
mainWindow.moduleSelector().selectModule('DICOMReaders')
return testPass
......@@ -23,7 +23,6 @@ class FiducialLayoutSwitchBug1914(ScriptedLoadableModule):
This file was originally developed by Nicole Aucoin, BWH and was partially funded by NIH grant 3P41RR013218-12S1.
"""
#
# qFiducialLayoutSwitchBug1914Widget
#
......@@ -107,45 +106,6 @@ class FiducialLayoutSwitchBug1914Logic(ScriptedLoadableModuleLogic):
# for future testing: take into account the volume voxel size
self.maximumRASDifference = 1.0;
def takeScreenshot(self,name,description,type=-1):
# show the message even if not taking a screen shot
self.delayDisplay(description)
if self.enableScreenshots == 0:
return
lm = slicer.app.layoutManager()
# switch on the type to get the requested window
widget = 0
if type == slicer.qMRMLScreenShotDialog.FullLayout:
# full layout
widget = lm.viewport()
elif type == slicer.qMRMLScreenShotDialog.ThreeD:
# just the 3D window
widget = lm.threeDWidget(0).threeDView()
elif type == slicer.qMRMLScreenShotDialog.Red:
# red slice window
widget = lm.sliceWidget("Red")
elif type == slicer.qMRMLScreenShotDialog.Yellow:
# yellow slice window
widget = lm.sliceWidget("Yellow")
elif type == slicer.qMRMLScreenShotDialog.Green:
# green slice window
widget = lm.sliceWidget("Green")
else:
# default to using the full window
widget = slicer.util.mainWindow()
# reset the type so that the node is set correctly
type = slicer.qMRMLScreenShotDialog.FullLayout
# grab and convert to vtk image data
qimage = ctk.ctkWidgetsUtils.grabWidget(widget)
imageData = vtk.vtkImageData()
slicer.qMRMLUtils().qImageToVtkImageData(qimage,imageData)
annotationLogic = slicer.modules.annotations.logic()
annotationLogic.CreateSnapShot(name, description, type, self.screenshotScaleFactor, imageData)
def getFiducialSliceDisplayableManagerHelper(self,sliceName='Red'):
sliceWidget = slicer.app.layoutManager().sliceWidget(sliceName)
sliceView = sliceWidget.sliceView()
......@@ -162,7 +122,7 @@ class FiducialLayoutSwitchBug1914Logic(ScriptedLoadableModuleLogic):