Slicer crashes when I update a vtkDistancePolyDataFilter

Hi,

For my project, I need to compute the distance between 2 models (one loaded from a vtk file and the other one created by my own). To do that, I tried to use a vtkDistancePolyDataFilter. But when I’m updating this filter, Slicer crashes. So I can’t figure out the issue. If someone is able to help me.

image

The code looks fine, so most likely the input data is somehow invalid. Could you upload the scene and a script that we can run as is (after loading the scene) that reproduces the issue?

I’m not allowed to upload the scene (format isn’t accepted).

Here is the script that I run :

import logging
import os
from math import dist
import vtk
import numpy as np

import slicer, vtk, qt, SampleData
from slicer.ScriptedLoadableModule import *
from slicer.util import *
from vtkmodules.vtkFiltersCore import vtkTubeFilter
from vtkmodules.vtkFiltersSources import vtkLineSource
from vtkmodules.vtkRenderingCore import (
    vtkActor,
    vtkPolyDataMapper,
    vtkRenderWindow,
    vtkRenderWindowInteractor,
    vtkRenderer
)
from vtk.util.numpy_support import vtk_to_numpy


#%%
#
# Needle1
#

class Needle1(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)
        self.parent.title = "Needle1"  # TODO: make this more human readable by adding spaces
        self.parent.categories = ["Examples"]  # TODO: set categories (folders where the module shows up in the module selector)
        self.parent.dependencies = []  # TODO: add here list of module names that this module requires
        self.parent.contributors = ["Caroline Essert (University of Strasbourg)"]  # TODO: replace with "Firstname Lastname (Organization)"
        # TODO: update with short description of the module and a link to online module documentation
        self.parent.helpText = """
            This is an example of scripted loadable module bundled in an extension.
            See more information in <a href="https://github.com/organization/projectname#Needle1">module documentation</a>.
            """
        # TODO: replace with organization, grant and thanks
        self.parent.acknowledgementText = """
            This file was originally developed by Jean-Christophe Fillion-Robin, Kitware Inc., Andras Lasso, PerkLab,
            and Steve Pieper, Isomics, Inc. and was partially funded by NIH grant 3P41RR013218-12S1.
            """

#%%
#
# Needle1Widget
#

class Needle1Widget(ScriptedLoadableModuleWidget, VTKObservationMixin):
    """Uses ScriptedLoadableModuleWidget base class, available at:
    https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
    """

    def __init__(self, parent=None):
        """
        Called when the user opens the module the first time and the widget is initialized.
        """
        ScriptedLoadableModuleWidget.__init__(self, parent)
        VTKObservationMixin.__init__(self)  # needed for parameter node observation
        # store logic in a member variable
        self.logic = Needle1Logic()
       



    def setup(self):
        """
        Called when the user opens the module the first time and the widget is initialized.
        """
        # initialisation that needs to be here - don't remove
        ScriptedLoadableModuleWidget.setup(self)

        # Init button
        InitButton = qt.QPushButton("Init")
        self.layout.addWidget(InitButton)
        InitButton.connect('clicked(bool)', self.onInitButtonClicked)

        # Create Cylinder button
        CreateCyl = qt.QPushButton("Create Cylinder")
        self.layout.addWidget(CreateCyl)
        CreateCyl.connect('clicked(bool)', self.onCreateCylClicked)

        # Place Electrode button
        myButton = qt.QPushButton("Place Electrode")
        self.layout.addWidget(myButton)
        myButton.connect('clicked(bool)', self.onPlaceElectrodeButtonClicked)

        # Compute Distance button
        myButton = qt.QPushButton("Compute Distance")
        self.layout.addWidget(myButton)
        myButton.connect('clicked(bool)', self.onComputeDistanceButtonClicked)


    # Create Cylinder button callback function
    def onCreateCylClicked(self):
        # call logic function to print position of the first fiducial (requires a fiducial to be added beforehand to work properly)
        self.mySnode = self.logic.myCreateCylinder()



    def onPlaceElectrodeButtonClicked(self):
        self.logic.myPlacingfct()


    def onInitButtonClicked(self):
        self.logic.myInit()

    def onComputeDistanceButtonClicked(self):
        self.logic.myDistance()

#%%
#
# Needle1Logic
#

class Needle1Logic(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.
    Uses ScriptedLoadableModuleLogic base class, available at:
    https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
    """

    def __init__(self):
        """
        Called when the logic class is instantiated. Can be used for initializing member variables.
        """
        ScriptedLoadableModuleLogic.__init__(self)
        print('in init logic')
        #myP1 = slicer.modules.markups.logic().AddControlPoint(0, 0, 0)
        #myP2 = slicer.modules.markups.logic().AddControlPoint(150.0, 0, 0)
        try:
            f = getNode('F')         
            obsTag=f.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, self.modifyMyCylinder)
            print('Observer instantiated')
        except slicer.util.MRMLNodeNotFoundException:
            print("Please create a fiducial first, no observer yet")
        
    def myInit(self):
        try:
            f = getNode('F')         
            obsTag=f.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, self.modifyMyCylinder)
            print('Observer instantiated')
        except slicer.util.MRMLNodeNotFoundException:
            print("Please create a fiducial first, no observer yet")

    def myCreateCylinder(self, caller=None, event=None): 
        #je dois gerer la creation des points si ils exisent pas encore, sinon creer sans cesse de nouveaux doublets de points
        
        try :
            f = getNode('F')
            print('f found in try')
            #print(dir(self))
            #update the position of the cylinder
            self.modifyMyCylinder()
            print(self.myLine)
            print(self.myCylinder)
            
        except slicer.util.MRMLNodeNotFoundException:
            print("I'll create 2 fiducials first")
            myP1 = slicer.modules.markups.logic().AddControlPoint(0, 0, 0)
            myP2 = slicer.modules.markups.logic().AddControlPoint(150.0, 0, 0)
            
            f = getNode('F')
            # initilize a position
            pos=[0,0,0]
            # get the first fiducial's position in pos (fiducial of index=0)
            f.GetNthFiducialPosition(0,pos)
            #print(pos)
            posF2=[0,0,0]
            f.GetNthFiducialPosition(1,posF2)
            #print(posF2)

            lineSource = vtkLineSource()
            lineSource.SetPoint1(pos)
            lineSource.SetPoint2(posF2)

            lineMapper = vtkPolyDataMapper()
            lineMapper.SetInputConnection(lineSource.GetOutputPort())

            lineActor = vtkActor()
            lineActor.SetMapper(lineMapper)

            # Create tube filter
            tubeFilter = vtkTubeFilter()
            tubeFilter.SetInputConnection(lineSource.GetOutputPort())
            tubeFilter.SetRadius(1)
            tubeFilter.SetNumberOfSides(50)
            tubeFilter.Update()

            self.myLine = lineSource
            self.myCylinder = tubeFilter

            sNode=slicer.modules.models.logic().AddModel(tubeFilter.GetOutputPort())
            sNode.SetAndObservePolyData(tubeFilter.GetOutput())
            sDisplayNode=sNode.GetDisplayNode()
            return sNode

    def myPlacingfct(self):
        right_snt = slicer.mrmlScene.GetFirstNodeByName('63_stn_right.vtk')
        pointCoordinates = slicer.util.arrayFromModelPoints(right_snt)
        pd = right_snt.GetPolyData().GetPoints().GetData() #right_snt.GetPolyData().GetCenter()
        centroid_right_snt = np.average(vtk_to_numpy(pd), axis=0)
        f = getNode('F')
        posF2 = [20,20,20]
        f.SetNthFiducialPositionFromArray(0,centroid_right_snt)
        f.GetNthFiducialPosition(1,posF2)
       
        self.myLine.SetPoint1(centroid_right_snt)
        self.myLine.SetPoint2(posF2)
        self.myLine.Update()
        self.myCylinder.Update()
        print('Electrode placed in the middle of the right STN')

    def modifyMyCylinder(self,caller=None, event=None):
        print('Position of one fiducial changed, adjusting the cylinder')
        f = getNode('F')
        pos=[0,0,0]
        f.GetNthFiducialPosition(0,pos)
        posF2=[0,0,0]
        f.GetNthFiducialPosition(1,posF2)
        self.myLine.SetPoint1(pos)
        self.myLine.SetPoint2(posF2)
        self.myLine.Update()
        self.myCylinder.Update()

    def myDistance(self,caller=None,event=None):
        right_sulci = slicer.mrmlScene.GetFirstNodeByName('63_sulci_right.vtk')
        left_sulci = slicer.mrmlScene.GetFirstNodeByName('63_sulci_left.vtk')
        
        fourth_ventricule = slicer.mrmlScene.GetFirstNodeByName('63_fourth_ventricle.vtk')
        third_ventricule = slicer.mrmlScene.GetFirstNodeByName('63_third_ventricle.vtk')
        lat_left_ventricule = slicer.mrmlScene.GetFirstNodeByName('63_lateral_ventricle_left.vtk')
        lat_right_ventricule = slicer.mrmlScene.GetFirstNodeByName('63_lateral_ventricle_right.vtk')

        tab_sulci = [right_sulci,left_sulci]
        tab_ventricule = [fourth_ventricule,third_ventricule,lat_left_ventricule,lat_right_ventricule]
        needle = self.myCylinder.GetOutputPort()
        print(type(self.myCylinder.GetOutputPort()))
        print(type(lat_left_ventricule.GetPolyDataConnection()))
        distanceFilter = vtk.vtkDistancePolyDataFilter()
        distanceFilter.SetInputConnection(0, needle)
        distanceFilter.SetInputConnection(1, lat_left_ventricule.GetPolyDataConnection()) #self.myCylinder.GetOutputPort()
        #distanceFilter.SignedDistanceOff()
        distanceFilter.Update()
        print(type(distanceFilter))
        #print(distanceFilter.GetOutput().GetPointData().GetScalars().GetRange()[0])

"""         for vec in tab_sulci:
            print(type(vec))
            print('////////////////////\n')

        for vec in tab_ventricule:
            print(type(vec))
            print('////////////////////\n')
 """
""" 
        print('Distance to ventricules : ')
 """

#%%
#
# Needle1Test
#

class Needle1Test(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 setUp(self):
        """ Do whatever is needed to reset the state - typically a scene clear will be enough.
        """
        # open the MRBrainTumor1 sample dataset
        sampleDataLogic = SampleData.SampleDataLogic()
        masterVolumeNode = sampleDataLogic.downloadMRBrainTumor1()

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

    def test_Needle11(self):
        """ Ideally you should have several levels of tests.  At the lowest level
        tests should 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.
        """

        # quick message box to inform that the test is starting
        self.delayDisplay("Starting the test")

        # create node to store fiducials
        markupsNodeID = slicer.modules.markups.logic().AddNewFiducialNode()
        markupsNode = getNode(markupsNodeID)
        markupsNode.SetName("F")
        # add one fiducial
        markupsNode.AddFiducial(6.4, 35.1, 0.7)

        # get the logic
        logic = Needle1Logic()
        # call function printPosF1 to test it on the previously added fiducial
        #logic.printPosF1()

        # quick message box to inform that the test has successfully ended
        self.delayDisplay('Test passed')


As an exemple, the scene looks like this :
image

My goal is to compute the distance between the cylinder ‘Model’ created with the function ‘myCreateCylinder’ and one of the ventricule.

You can upload to dropbox, google drive, or similar and post the link here.

Thanks for the tips.

Here is the link : Slicer_project - Google Drive
DBS contains the scene and test contains my module

How can we reproduce the issue? Can you copy here a code snippet that we can copy-paste into thr Python console after we loaded the scene?

Yes sure,
So after loading the scene, here is the code :

myP1 = slicer.modules.markups.logic().AddControlPoint(0, 0, 0)
myP2 = slicer.modules.markups.logic().AddControlPoint(150.0, 0, 0)

lineSource = vtk.vtkLineSource()
lineSource.SetPoint1([0,0,0])
lineSource.SetPoint2([150,0,0])

tubeFilter = vtk.vtkTubeFilter()
tubeFilter.SetInputConnection(lineSource.GetOutputPort())
tubeFilter.SetRadius(1)
tubeFilter.SetNumberOfSides(50)
tubeFilter.Update()

sNode=slicer.modules.models.logic().AddModel(tubeFilter.GetOutputPort())
sNode.SetAndObservePolyData(tubeFilter.GetOutput())
sDisplayNode=sNode.GetDisplayNode()

right_sulci = slicer.mrmlScene.GetFirstNodeByName('63_sulci_right.vtk')

distanceFilter = vtk.vtkDistancePolyDataFilter()
distanceFilter.SetInputConnection(0, tubeFilter.GetOutputPort())
distanceFilter.SetInputConnection(1, right_sulci.GetPolyDataConnection())
distanceFilter.Update()

When you’ll execute the last line (“distanceFilter.Update()”), nothing will happen and Slicer will crash.

Thanks

Probably right_sulci.GetPolyDataConnection() is null because the model was read from a file. Try using distanceFilter.SetInputData(1, right_sulci.GetPolyData()) instead.

I’ve debugged into this and GetPolyDataConnection() is all right.

The problem is a bug in VTK! vtkDistancePolyDataFilter crashes for any cell types that uses subId (such as vtkTriangleStrip) input because subId is an input argument of EvaluateLocatio and it is not initialized:

Please report this to the VTK forum or bugtracker and post the link here for future reference.

There is a simple workaround: apply a vtkTriangleFilter on the tube filter output.

1 Like