def run(self, enableScreenshots=0, screenshotScaleFactor=1): """ Run the actual algorithm """ self.delayDisplay('Running the aglorithm') self.enableScreenshots = enableScreenshots self.screenshotScaleFactor = screenshotScaleFactor # Start in conventional layout lm = slicer.app.layoutManager() lm.setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutConventionalView) # without this delayed display, when running from the cmd line Slicer starts # up in a different layout and the seed won't get rendered in the right spot self.delayDisplay("Conventional view") # Download MRHead from sample data import SampleData sampleDataLogic = SampleData.SampleDataLogic() print("Getting MR Head Volume") mrHeadVolume = sampleDataLogic.downloadMRHead() # Place a fiducial on the red slice markupsLogic = slicer.modules.markups.logic() eye = [33.4975, 79.4042, -10.2143] fidIndex = markupsLogic.AddFiducial(eye[0], eye[1], eye[2]) fidID = markupsLogic.GetActiveListID() fidNode = slicer.mrmlScene.GetNodeByID(fidID) self.delayDisplay("Placed a fiducial") # Pan and zoom sliceWidget = slicer.app.layoutManager().sliceWidget('Red') sliceLogic = sliceWidget.sliceLogic() compositeNode = sliceLogic.GetSliceCompositeNode() sliceNode = sliceLogic.GetSliceNode() sliceNode.SetXYZOrigin(-71.7, 129.7, 0.0) sliceNode.SetFieldOfView(98.3, 130.5, 1.0) self.delayDisplay("Panned and zoomed") # Get the seed widget seed location startingSeedDisplayCoords = [0.0, 0.0, 0.0] helper = self.getFiducialSliceDisplayableManagerHelper('Red') if helper != None: seedWidget = helper.GetWidget(fidNode) seedRepresentation = seedWidget.GetSeedRepresentation() handleRep = seedRepresentation.GetHandleRepresentation(fidIndex) startingSeedDisplayCoords = handleRep.GetDisplayPosition() print('Starting seed display coords = %d, %d, %d' % (startingSeedDisplayCoords[0], startingSeedDisplayCoords[1], startingSeedDisplayCoords[2])) self.takeScreenshot('FiducialLayoutSwitchBug1914-StartingPosition', 'Fiducial starting position', slicer.qMRMLScreenShotDialog().Red) # Switch to red slice only lm.setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpRedSliceView) self.delayDisplay("Red Slice only") # Switch to conventional layout lm.setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutConventionalView) self.delayDisplay("Conventional layout") # Get the current seed widget seed location endingSeedDisplayCoords = [0.0, 0.0, 0.0] helper = self.getFiducialSliceDisplayableManagerHelper('Red') if helper != None: seedWidget = helper.GetWidget(fidNode) seedRepresentation = seedWidget.GetSeedRepresentation() handleRep = seedRepresentation.GetHandleRepresentation(fidIndex) endingSeedDisplayCoords = handleRep.GetDisplayPosition() print('Ending seed display coords = %d, %d, %d' % (endingSeedDisplayCoords[0], endingSeedDisplayCoords[1], endingSeedDisplayCoords[2])) self.takeScreenshot('FiducialLayoutSwitchBug1914-EndingPosition', 'Fiducial ending position', slicer.qMRMLScreenShotDialog().Red) # Compare to original seed widget location diff = math.pow( (startingSeedDisplayCoords[0] - endingSeedDisplayCoords[0]), 2) + math.pow( (startingSeedDisplayCoords[1] - endingSeedDisplayCoords[1]), 2) + math.pow((startingSeedDisplayCoords[2] - endingSeedDisplayCoords[2]), 2) if diff != 0.0: diff = math.sqrt(diff) self.delayDisplay( "Difference between starting and ending seed display coordinates = %g" % diff) if diff > 1.0: # raise Exception("Display coordinate difference is too large!\nExpected < 1.0 but got %g" % (diff)) print( "Display coordinate difference is too large!\nExpected < 1.0 but got %g" % (diff)) return False if enableScreenshots == 1: # compare the screen snapshots startView = slicer.mrmlScene.GetFirstNodeByName( 'FiducialLayoutSwitchBug1914-StartingPosition') startShot = startView.GetScreenShot() endView = slicer.mrmlScene.GetFirstNodeByName( 'FiducialLayoutSwitchBug1914-EndingPosition') endShot = endView.GetScreenShot() imageMath = vtk.vtkImageMathematics() imageMath.SetOperationToSubtract() imageMath.SetInput1(startShot) imageMath.SetInput2(endShot) imageMath.Update() shotDiff = imageMath.GetOutput() # save it as a scene view annotationLogic = slicer.modules.annotations.logic() annotationLogic.CreateSnapShot( "FiducialLayoutSwitchBug1914-Diff", "Difference between starting and ending fiducial seed positions", slicer.qMRMLScreenShotDialog().Red, screenshotScaleFactor, shotDiff) # calculate the image difference imageStats = vtk.vtkImageHistogramStatistics() imageStats.SetInput(shotDiff) imageStats.GenerateHistogramImageOff() imageStats.Update() meanVal = imageStats.GetMean() self.delayDisplay("Mean of image difference = %g" % meanVal) if meanVal > 5.0: # raise Exception("Image difference is too great!\nExpected <= 5.0, but got %g" % (meanVal)) print( "Image difference is too great!\nExpected <= 5.0, but got %g" % (meanVal)) return False self.delayDisplay('Test passed!') return True
def updateWindowLevelRectangle(self, sliceLayer, xyBounds): # conversion as done in LabelEffect.py:applyImageMask xlo, xhi, ylo, yhi, zlo, zhi = xyBounds if xlo == xhi or ylo == yhi: return sliceNode = sliceLayer.GetVolumeNode() sliceImage = sliceNode.GetImageData() if not sliceImage: return [0,0,0,0] xyToIJK = sliceLayer.GetXYToIJKTransform() tlIJK = xyToIJK.TransformPoint( (xlo, yhi, 0) ) trIJK = xyToIJK.TransformPoint( (xhi, yhi, 0) ) blIJK = xyToIJK.TransformPoint( (xlo, ylo, 0) ) brIJK = xyToIJK.TransformPoint( (xhi, ylo, 0) ) # # get the mask bounding box in ijk coordinates # - get the xy bounds # - transform to ijk # - clamp the bounds to the dimensions of the label image # # do the clamping of the four corners dims = sliceImage.GetDimensions() tl = [0,] * 3 tr = [0,] * 3 bl = [0,] * 3 br = [0,] * 3 corners = ((tlIJK, tl),(trIJK, tr),(blIJK, bl),(brIJK, br)) for corner,clampedCorner in corners: for d in xrange(3): clamped = int(round(corner[d])) if clamped < 0: clamped = 0 if clamped >= dims[d]: clamped = dims[d]-1 clampedCorner[d] = clamped # calculate the statistics for the selected region clip = vtk.vtkImageClip() extentMin = [min(tl[0],min(tr[0],min(bl[0],br[0]))),min(tl[1],min(tr[1],min(bl[1],br[1]))),min(tl[2],min(tr[2],min(bl[2],br[2])))] extentMax = [max(tl[0],max(tr[0],max(bl[0],br[0]))),max(tl[1],max(tr[1],max(bl[1],br[1]))),max(tl[2],max(tr[2],max(bl[2],br[2])))] clip.SetOutputWholeExtent(extentMin[0],extentMax[0],extentMin[1],extentMax[1],extentMin[2],extentMax[2]) if vtk.VTK_MAJOR_VERSION <= 5: clip.SetInput(sliceNode.GetImageData()) else: clip.SetInputData(sliceNode.GetImageData()) clip.ClipDataOn() clip.Update() stats = vtk.vtkImageHistogramStatistics() if vtk.VTK_MAJOR_VERSION <= 5: stats.SetInput(clip.GetOutput()) else: stats.SetInputData(clip.GetOutput()) stats.Update() minIntensity = stats.GetMinimum() maxIntensity = stats.GetMaximum() sliceNode.GetDisplayNode().SetAutoWindowLevel(False) sliceNode.GetDisplayNode().SetAutoWindowLevel(0) sliceNode.GetDisplayNode().SetWindowLevel(maxIntensity-minIntensity, (maxIntensity-minIntensity)/2.+minIntensity) sliceNode.GetDisplayNode().Modified()
def updateWindowLevelRectangle(self, sliceLayer, xyBounds): # conversion as done in LabelEffect.py:applyImageMask xlo, xhi, ylo, yhi, zlo, zhi = xyBounds if xlo == xhi or ylo == yhi: return sliceNode = sliceLayer.GetVolumeNode() sliceImage = sliceNode.GetImageData() if not sliceImage: return [0, 0, 0, 0] xyToIJK = sliceLayer.GetXYToIJKTransform() tlIJK = xyToIJK.TransformPoint((xlo, yhi, 0)) trIJK = xyToIJK.TransformPoint((xhi, yhi, 0)) blIJK = xyToIJK.TransformPoint((xlo, ylo, 0)) brIJK = xyToIJK.TransformPoint((xhi, ylo, 0)) # # get the mask bounding box in ijk coordinates # - get the xy bounds # - transform to ijk # - clamp the bounds to the dimensions of the label image # # do the clamping of the four corners dims = sliceImage.GetDimensions() tl = [0] * 3 tr = [0] * 3 bl = [0] * 3 br = [0] * 3 corners = ((tlIJK, tl), (trIJK, tr), (blIJK, bl), (brIJK, br)) for corner, clampedCorner in corners: for d in xrange(3): clamped = int(round(corner[d])) if clamped < 0: clamped = 0 if clamped >= dims[d]: clamped = dims[d] - 1 clampedCorner[d] = clamped # calculate the statistics for the selected region clip = vtk.vtkImageClip() extentMin = [ min(tl[0], min(tr[0], min(bl[0], br[0]))), min(tl[1], min(tr[1], min(bl[1], br[1]))), min(tl[2], min(tr[2], min(bl[2], br[2]))), ] extentMax = [ max(tl[0], max(tr[0], max(bl[0], br[0]))), max(tl[1], max(tr[1], max(bl[1], br[1]))), max(tl[2], max(tr[2], max(bl[2], br[2]))), ] clip.SetOutputWholeExtent(extentMin[0], extentMax[0], extentMin[1], extentMax[1], extentMin[2], extentMax[2]) if vtk.VTK_MAJOR_VERSION <= 5: clip.SetInput(sliceNode.GetImageData()) else: clip.SetInputData(sliceNode.GetImageData()) clip.ClipDataOn() clip.Update() stats = vtk.vtkImageHistogramStatistics() if vtk.VTK_MAJOR_VERSION <= 5: stats.SetInput(clip.GetOutput()) else: stats.SetInputData(clip.GetOutput()) stats.Update() minIntensity = stats.GetMinimum() maxIntensity = stats.GetMaximum() sliceNode.GetDisplayNode().SetAutoWindowLevel(False) sliceNode.GetDisplayNode().SetAutoWindowLevel(0) sliceNode.GetDisplayNode().SetWindowLevel( maxIntensity - minIntensity, (maxIntensity - minIntensity) / 2.0 + minIntensity ) sliceNode.GetDisplayNode().Modified()
def test_ResectionVolume1(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 # resectionVolumeWidget = slicer.modules.ResectionVolumeWidget # Confirm that generate surface checkbox will not stay checked resectionVolumeWidget.generateSurface.setChecked(True) self.assertTrue(resectionVolumeWidget.generateSurface.isChecked() == False) # Data is in local directory currently for initial development slicer.util.loadMarkupsFiducialList("C:\SlicerTestData\ResectionVolumePoints.fcsv") slicer.util.loadModel("C:\SlicerTestData\ResectionVolumeModel.vtk") slicer.util.loadLabelVolume("C:\SlicerTestData\ResectionVolumeTestLabel.nrrd") slicer.util.loadLabelVolume("C:\SlicerTestData\RecoloredResectionVolumeTestLabel.nrrd") # Set fiducial points node fiducialNode = slicer.util.getNode("ResectionVolumePoints") resectionVolumeWidget.fiducialSelector.setCurrentNode(fiducialNode) # Confirm that generate surface checkbox will not stay checked resectionVolumeWidget.generateSurface.setChecked(True) self.assertTrue(resectionVolumeWidget.generateSurface.isChecked() == False) # Set model node testModelNode = slicer.mrmlScene.CreateNodeByClass("vtkMRMLModelNode") testModelNode.SetName("ResectionVolumeModelTest") slicer.mrmlScene.AddNode(testModelNode) resectionVolumeWidget.modelSelector.setCurrentNode(testModelNode) # Check the generate surface box resectionVolumeWidget.generateSurface.setChecked(True) # Confirm that generate surface checkbox stays checked self.assertTrue(resectionVolumeWidget.generateSurface.isChecked()) # Compare newly generated model with loaded model by determining if # the maximum distance between the 2 sets of polydata is less than # a desired value loadedModelNode = slicer.util.getNode("ResectionVolumeModel") distanceFilter = vtk.vtkDistancePolyDataFilter() distanceFilter.SetInputData(0, testModelNode.GetPolyData()) distanceFilter.SetInputData(1, loadedModelNode.GetPolyData()) distanceFilter.Update() distancePolyData = distanceFilter.GetOutput() distanceRange = distancePolyData.GetScalarRange() maxDistance = max(abs(distanceRange[0]),abs(distanceRange[1])) self.assertTrue(maxDistance < 0.0001) # What value for cutoff # Recolor the test label labelNode = slicer.util.getNode("ResectionVolumeTestLabel") loadedLabelNode = slicer.util.getNode("RecoloredResectionVolumeTestLabel") resectionVolumeWidget.labelSelector.setCurrentNode(labelNode) resectionVolumeWidget.initialLabelValueSelector.setValue(1) resectionVolumeWidget.outputLabelValueSelector.setValue(2) resectionVolumeWidget.onRecolorLabelMap() # Compare the recolored test label with the loaded recolored test label # by subtracting the 2 images (which should be the same) and then finding # the min/max values, (which should be 0) imageMath = vtk.vtkImageMathematics() imageMath.SetOperationToSubtract() imageMath.SetInput1Data(labelNode.GetImageData()) imageMath.SetInput2Data(loadedLabelNode.GetImageData()) imageMath.Update() imageStatistics = vtk.vtkImageHistogramStatistics() imageStatistics.SetInputData(imageMath.GetOutput()) minimumValue = imageStatistics.GetMinimum() maximumValue = imageStatistics.GetMaximum() self.assertTrue(minimumValue == maximumValue == 0) self.delayDisplay('Test passed!')
def test_ResectionVolume1(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") slicer.util.selectModule('ResectionVolume') resectionVolumeWidget = slicer.modules.ResectionVolumeWidget # Confirm that generate surface checkbox will not stay checked resectionVolumeWidget.generateSurface.setChecked(True) self.assertTrue( resectionVolumeWidget.generateSurface.isChecked() == False) # Data is in local directory currently for initial development dir = os.path.dirname(__file__) slicer.util.loadMarkupsFiducialList( dir + "/TestData/ResectionVolumePoints.fcsv") slicer.util.loadModel(dir + "/TestData/ResectionVolumeModel.vtk") slicer.util.loadLabelVolume(dir + "/TestData/ResectionVolumeTestLabel.nrrd") slicer.util.loadLabelVolume( dir + "/TestData/RecoloredResectionVolumeTestLabel.nrrd") # Set fiducial points node fiducialNode = slicer.util.getNode("ResectionVolumePoints") resectionVolumeWidget.fiducialSelector.setCurrentNode(fiducialNode) # Confirm that generate surface checkbox will not stay checked resectionVolumeWidget.generateSurface.setChecked(True) self.assertTrue( resectionVolumeWidget.generateSurface.isChecked() == False) # Set model node testModelNode = slicer.mrmlScene.CreateNodeByClass("vtkMRMLModelNode") testModelNode.SetName("ResectionVolumeModelTest") slicer.mrmlScene.AddNode(testModelNode) resectionVolumeWidget.modelSelector.setCurrentNode(testModelNode) # Check the generate surface box resectionVolumeWidget.generateSurface.setChecked(True) # Confirm that generate surface checkbox stays checked self.assertTrue(resectionVolumeWidget.generateSurface.isChecked()) # Compare newly generated model with loaded model by determining if # the maximum distance between the 2 sets of polydata is less than # a desired value loadedModelNode = slicer.util.getNode("ResectionVolumeModel") distanceFilter = vtk.vtkDistancePolyDataFilter() distanceFilter.SetInputData(0, testModelNode.GetPolyData()) distanceFilter.SetInputData(1, loadedModelNode.GetPolyData()) distanceFilter.Update() distancePolyData = distanceFilter.GetOutput() distanceRange = distancePolyData.GetScalarRange() maxDistance = max(abs(distanceRange[0]), abs(distanceRange[1])) self.assertTrue(maxDistance < 0.0001) # What value for cutoff self.delayDisplay('Generate Model Test Passed!') # Recolor the test label labelNode = slicer.util.getNode("ResectionVolumeTestLabel") loadedLabelNode = slicer.util.getNode( "RecoloredResectionVolumeTestLabel") resectionVolumeWidget.labelSelector.setCurrentNode(labelNode) resectionVolumeWidget.initialLabelValueSelector.setValue(1) resectionVolumeWidget.outputLabelValueSelector.setValue(2) resectionVolumeWidget.onRecolorLabelMap() # Compare the recolored test label with the loaded recolored test label # by subtracting the 2 images (which should be the same) and then finding # the min/max values, (which should be 0) imageMath = vtk.vtkImageMathematics() imageMath.SetOperationToSubtract() imageMath.SetInput1Data(labelNode.GetImageData()) imageMath.SetInput2Data(loadedLabelNode.GetImageData()) imageMath.Update() imageStatistics = vtk.vtkImageHistogramStatistics() imageStatistics.SetInputData(imageMath.GetOutput()) minimumValue = imageStatistics.GetMinimum() maximumValue = imageStatistics.GetMaximum() self.assertTrue(minimumValue == maximumValue == 0) self.delayDisplay('Test passed!')
def run(self,enableScreenshots=0,screenshotScaleFactor=1): """ Run the actual algorithm """ self.delayDisplay('Running the aglorithm') self.enableScreenshots = enableScreenshots self.screenshotScaleFactor = screenshotScaleFactor # Start in conventional layout lm = slicer.app.layoutManager() lm.setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutConventionalView) # without this delayed display, when running from the cmd line Slicer starts # up in a different layout and the seed won't get rendered in the right spot self.delayDisplay("Conventional view") # Download MRHead from sample data import SampleData sampleDataLogic = SampleData.SampleDataLogic() print("Getting MR Head Volume") mrHeadVolume = sampleDataLogic.downloadMRHead() # Place a fiducial on the red slice markupsLogic = slicer.modules.markups.logic() eye = [33.4975, 79.4042, -10.2143] fidIndex = markupsLogic.AddFiducial(eye[0], eye[1], eye[2]) fidID = markupsLogic.GetActiveListID() fidNode = slicer.mrmlScene.GetNodeByID(fidID) self.delayDisplay("Placed a fiducial") # Pan and zoom sliceWidget = slicer.app.layoutManager().sliceWidget('Red') sliceLogic = sliceWidget.sliceLogic() compositeNode = sliceLogic.GetSliceCompositeNode() sliceNode = sliceLogic.GetSliceNode() sliceNode.SetXYZOrigin(-71.7, 129.7, 0.0) sliceNode.SetFieldOfView(98.3, 130.5, 1.0) self.delayDisplay("Panned and zoomed") # Get the seed widget seed location startingSeedDisplayCoords = [0.0, 0.0, 0.0] helper = self.getFiducialSliceDisplayableManagerHelper('Red') if helper != None: seedWidget = helper.GetWidget(fidNode) seedRepresentation = seedWidget.GetSeedRepresentation() handleRep = seedRepresentation.GetHandleRepresentation(fidIndex) startingSeedDisplayCoords = handleRep.GetDisplayPosition() print('Starting seed display coords = %d, %d, %d' % (startingSeedDisplayCoords[0], startingSeedDisplayCoords[1], startingSeedDisplayCoords[2])) self.takeScreenshot('FiducialLayoutSwitchBug1914-StartingPosition','Fiducial starting position',slicer.qMRMLScreenShotDialog().Red) # Switch to red slice only lm.setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpRedSliceView) self.delayDisplay("Red Slice only") # Switch to conventional layout lm.setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutConventionalView) self.delayDisplay("Conventional layout") # Get the current seed widget seed location endingSeedDisplayCoords = [0.0, 0.0, 0.0] helper = self.getFiducialSliceDisplayableManagerHelper('Red') if helper != None: seedWidget = helper.GetWidget(fidNode) seedRepresentation = seedWidget.GetSeedRepresentation() handleRep = seedRepresentation.GetHandleRepresentation(fidIndex) endingSeedDisplayCoords = handleRep.GetDisplayPosition() print('Ending seed display coords = %d, %d, %d' % (endingSeedDisplayCoords[0], endingSeedDisplayCoords[1], endingSeedDisplayCoords[2])) self.takeScreenshot('FiducialLayoutSwitchBug1914-EndingPosition','Fiducial ending position',slicer.qMRMLScreenShotDialog().Red) # Compare to original seed widget location diff = math.pow((startingSeedDisplayCoords[0] - endingSeedDisplayCoords[0]),2) + math.pow((startingSeedDisplayCoords[1] - endingSeedDisplayCoords[1]),2) + math.pow((startingSeedDisplayCoords[2] - endingSeedDisplayCoords[2]),2) if diff != 0.0: diff = math.sqrt(diff) self.delayDisplay("Difference between starting and ending seed display coordinates = %g" % diff) if diff > 1.0: # raise Exception("Display coordinate difference is too large!\nExpected < 1.0 but got %g" % (diff)) print("Display coordinate difference is too large!\nExpected < 1.0 but got %g" % (diff)) return False if enableScreenshots == 1: # compare the screen snapshots startView = slicer.mrmlScene.GetFirstNodeByName('FiducialLayoutSwitchBug1914-StartingPosition') startShot = startView.GetScreenShot() endView = slicer.mrmlScene.GetFirstNodeByName('FiducialLayoutSwitchBug1914-EndingPosition') endShot = endView.GetScreenShot() imageMath = vtk.vtkImageMathematics() imageMath.SetOperationToSubtract() imageMath.SetInput1(startShot) imageMath.SetInput2(endShot) imageMath.Update() shotDiff = imageMath.GetOutput() # save it as a scene view annotationLogic = slicer.modules.annotations.logic() annotationLogic.CreateSnapShot("FiducialLayoutSwitchBug1914-Diff", "Difference between starting and ending fiducial seed positions",slicer.qMRMLScreenShotDialog().Red, screenshotScaleFactor, shotDiff) # calculate the image difference imageStats = vtk.vtkImageHistogramStatistics() imageStats.SetInput(shotDiff) imageStats.GenerateHistogramImageOff() imageStats.Update() meanVal = imageStats.GetMean() self.delayDisplay("Mean of image difference = %g" % meanVal) if meanVal > 5.0: # raise Exception("Image difference is too great!\nExpected <= 5.0, but got %g" % (meanVal)) print("Image difference is too great!\nExpected <= 5.0, but got %g" % (meanVal)) return False self.delayDisplay('Test passed!') return True