2
votes

I'm trying to get into Extension programming in 3DSlicer using Python. There is a tutorial online. Unfortunately there is a problem with the third example script "HelloSharpen". I did the exact same thing they did but I get this error:

Traceback (most recent call last):
  File "C:/Users/johan/Desktop/HelloPythonSlicer4/helloPython/code/HelloSharpen.py", line 105, in onApply
    laplacian.SetInput(inputVolume.GetImageData())
AttributeError: 'vtkImagingGeneralPython.vtkImageLaplacian' object has no attribute 'SetInput'

I solved this by changing laplacian.SetInput(inputVolume.GetImageData()) to laplacian.SetInputData(inputVolume.GetImageData()) because I read that they changed this in the newer versions of VTK.

However when I try to run this a new error comes up:

Traceback (most recent call last):
  File "C:/Users/johan/Desktop/HelloPythonSlicer4/helloPython/code/HelloSharpen.py", line 107, in onApply
    laplacian.GetOutput().Update()
AttributeError: 'vtkCommonDataModelPython.vtkImageData' object has no attribute 'Update'

It seems that laplacian.GetOutput().Update() is causing problems so I tried to find something on the Internet if they also changed this in the newer versions of VTK but I couldn't find anything. I tried to change this into "UpdateData" but this doesn't work. Do you know if they also changed this and if yes, do you know what I should replace this with?

Here is the full code for "HelloSharpen":

from __main__ import vtk, qt, ctk, slicer

#
# HelloSharpen
#

class HelloSharpen:
  def __init__(self, parent):
    parent.title = "Hello Python Part D - Sharpen"
    parent.categories = ["Examples"]
    parent.dependencies = []
    parent.contributors = ["Jean-Christophe Fillion-Robin (Kitware)",
                           "Steve Pieper (Isomics)",
                           "Sonia Pujol (BWH)"] # replace with "Firstname Lastname (Org)"
    parent.helpText = """
    Example of scripted loadable extension for the HelloSharpen tutorial.
    """
    parent.acknowledgementText = """
    This file was originally developed by Jean-Christophe Fillion-Robin, Kitware Inc.,
Steve Pieper, Isomics, Inc., and Sonia Pujol, Brigham and Women's Hospital and was 
partially funded by NIH grant 3P41RR013218-12S1 (NAC) and is part of the National Alliance 
for Medical Image Computing (NA-MIC), funded by the National Institutes of Health through the 
NIH Roadmap for Medical Research, Grant U54 EB005149.""" # replace with organization, grant and thanks.
    self.parent = parent

#
# qHelloPythonWidget
#

class HelloSharpenWidget:
  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):
    # Collapsible button
    self.laplaceCollapsibleButton = ctk.ctkCollapsibleButton()
    self.laplaceCollapsibleButton.text = "Sharpen Operator"
    self.layout.addWidget(self.laplaceCollapsibleButton)

    # Layout within the laplace collapsible button
    self.laplaceFormLayout = qt.QFormLayout(self.laplaceCollapsibleButton)

    #
    # the volume selectors
    #
    self.inputFrame = qt.QFrame(self.laplaceCollapsibleButton)
    self.inputFrame.setLayout(qt.QHBoxLayout())
    self.laplaceFormLayout.addWidget(self.inputFrame)
    self.inputSelector = qt.QLabel("Input Volume: ", self.inputFrame)
    self.inputFrame.layout().addWidget(self.inputSelector)
    self.inputSelector = slicer.qMRMLNodeComboBox(self.inputFrame)
    self.inputSelector.nodeTypes = ( ("vtkMRMLScalarVolumeNode"), "" )
    self.inputSelector.addEnabled = False
    self.inputSelector.removeEnabled = False
    self.inputSelector.setMRMLScene( slicer.mrmlScene )
    self.inputFrame.layout().addWidget(self.inputSelector)

    self.outputFrame = qt.QFrame(self.laplaceCollapsibleButton)
    self.outputFrame.setLayout(qt.QHBoxLayout())
    self.laplaceFormLayout.addWidget(self.outputFrame)
    self.outputSelector = qt.QLabel("Output Volume: ", self.outputFrame)
    self.outputFrame.layout().addWidget(self.outputSelector)
    self.outputSelector = slicer.qMRMLNodeComboBox(self.outputFrame)
    self.outputSelector.nodeTypes = ( ("vtkMRMLScalarVolumeNode"), "" )
    self.outputSelector.setMRMLScene( slicer.mrmlScene )
    self.outputFrame.layout().addWidget(self.outputSelector)

    self.sharpen = qt.QCheckBox("Sharpen", self.laplaceCollapsibleButton)
    self.sharpen.toolTip = "When checked, subtract laplacian from input volume"
    self.sharpen.checked = True
    self.laplaceFormLayout.addWidget(self.sharpen) 


    # Apply button
    laplaceButton = qt.QPushButton("Apply")
    laplaceButton.toolTip = "Run the Laplace or Sharpen Operator."
    self.laplaceFormLayout.addWidget(laplaceButton)
    laplaceButton.connect('clicked(bool)', self.onApply)

    # Add vertical spacer
    self.layout.addStretch(1)

    # Set local var as instance attribute
    self.laplaceButton = laplaceButton

  def onApply(self):
    inputVolume = self.inputSelector.currentNode()
    outputVolume = self.outputSelector.currentNode()
    if not (inputVolume and outputVolume):
      qt.QMessageBox.critical(
          slicer.util.mainWindow(),
          'Sharpen', 'Input and output volumes are required for Laplacian')
      return
    # run the filter
    laplacian = vtk.vtkImageLaplacian()
    laplacian.SetInputData(inputVolume.GetImageData())
    laplacian.SetDimensionality(3)
    laplacian.GetOutput().Update()
    ijkToRAS = vtk.vtkMatrix4x4()
    inputVolume.GetIJKToRASMatrix(ijkToRAS)
    outputVolume.SetIJKToRASMatrix(ijkToRAS)
    outputVolume.SetAndObserveImageData(laplacian.GetOutput())

    # optionally subtract laplacian from original image
    if self.sharpen.checked:
      parameters = {}
      parameters['inputVolume1'] = inputVolume.GetID()
      parameters['inputVolume2'] = outputVolume.GetID()
      parameters['outputVolume'] = outputVolume.GetID()
      slicer.cli.run( slicer.modules.subtractscalarvolumes, None, parameters, wait_for_completion=True ) 

    selectionNode = slicer.app.applicationLogic().GetSelectionNode()
    selectionNode.SetReferenceActiveVolumeID(outputVolume.GetID())
    slicer.app.applicationLogic().PropagateVolumeSelection(0)
1

1 Answers

3
votes

TL;DR Change laplacian.GetOutput().Update() to laplacian.Update().

Explanation: As per this link, there was a major change introduced in VTK 6. In summary, newer versions of VTK have separated algorithms and the data in two different class hierarchies. In newer versions of VTK, the Update() function can be called only on objects which have been derived from the vtkAlgorithm class. You can look at the inheritance diagram of vtkImageLaplacian here and it is indeed derived from vtkAlgorithm class. So laplacian.Update() will work.

As the name suggests vtkImageData is a data object. laplacian.GetOutput() returns a vtkImageData object and that's why you cannot call Update() function on it and therefore you get the error.