# how to speedup vtkCellPicker ?

**URL:** https://discourse.vtk.org/t/how-to-speedup-vtkcellpicker/4167
**Category:** Support
**Created:** [September 7, 2020, 9:11am UTC](https://discourse.vtk.org/t/how-to-speedup-vtkcellpicker/4167 "2020-09-07T09:11:29Z")
**Posts on this page:** 1
**Page:** 1

<div class="post-metadata">

### Author: ![ericpellegrini](https://discourse.vtk.org/user_avatar/discourse.vtk.org/ericpellegrini/32/2302_2.png) [@ericpellegrini](https://discourse.vtk.org/u/ericpellegrini)
#### Post date: [September 7, 2020, 9:11am UTC](https://discourse.vtk.org/t/how-to-speedup-vtkcellpicker/4167/1 "2020-09-07T09:11:29Z")

</div>

Hi all,

below is a snippet of a molecular viewer written using vtk/PyQt which I adapted from a colleague’s code. Basically, the viewer is quite efficient regarding the mouse interaction (translation, rotation, zoom). I also need to add a callback which gives me the index of the atom which is picked, this index being transferred in other part of the whole PyQt application through signal/slot mechanism. Here the troubles begin. To do so, a vtkCellPicker is used but I found that it was painfully slow to retrieve that index (see on\_pick method). Eventually, it even freezes my application. I tried to use a vtkCellLocator by doing:

> locator = vtk.vtkCellLocator()  
> locator.SetDataSet(self.\_polydata)  
> locator.BuildLocator()  
> picker.AddLocator(locator)

But it did not improve the situation.

I also tried other pickers which seem to be much much faster ( vtkPropPicker , vtkPointPicker ) but I do not know how to retrieve the index of the picked atom from those pickers. Would you have any idea how to make it ? Being new in vtk, code snippet would be very welcome. Many thanks in advance.

> import sys
> 
> import numpy as np
> 
> from PyQt5 import QtCore, QtWidgets
> 
> import vtk  
> from vtk.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor
> 
> vtk.vtkObject.GlobalWarningDisplayOff()
> 
> RGB\_COLOURS = {}  
> RGB\_COLOURS[“selection”] = (0, (1.00, 0.20, 1.00))  
> RGB\_COLOURS[“default”] = (1, (1.00, 0.90, 0.90))
> 
> CHEMICAL\_ELEMENTS = {}  
> CHEMICAL\_ELEMENTS[‘H’] = {‘vdw\_radius’: 1.09, ‘color’: ‘255;255;255’}  
> CHEMICAL\_ELEMENTS[‘C’] = {‘vdw\_radius’: 1.70, ‘color’: ‘0;255;0’}  
> CHEMICAL\_ELEMENTS[‘N’] = {‘vdw\_radius’: 1.55, ‘color’: ‘0;0;255’}  
> CHEMICAL\_ELEMENTS[‘O’] = {‘vdw\_radius’: 1.52, ‘color’: ‘255;0;0’}
> 
> NUMBER\_OF\_ATOMS = 100000
> 
> RESOLUTION = int(np.sqrt(5000000.0 / NUMBER\_OF\_ATOMS))  
> RESOLUTION = 10 if RESOLUTION \> 10 else RESOLUTION  
> RESOLUTION = 4 if RESOLUTION \< 4 else RESOLUTION
> 
> def color\_string\_to\_rgb(color):  
> “”"Convert a color stored in r;g;b format to [r/255.0,g/255.0,b/255.0] format.
> 
> ```
> Args:
> color (str): the color to convert
> """
> 
> if not color.strip():
> color = "1;1;1"
> 
> return np.array(color.split(';')).astype(np.float32)/255.
> 
> ```
> 
> def ndarray\_to\_vtkarray(colors, scales, n\_atoms):  
> “”"Convert the colors and scales NumPy arrays to vtk arrays.
> 
> ```
> Args:
> colors (numpy.array): the colors
> scales (numpy.array): the scales
> n_atoms (int): the number of atoms
> """
> # define the colours
> color_scalars = vtk.vtkFloatArray()
> color_scalars.SetNumberOfValues(len(colors))
> for i, c in enumerate(colors):
> color_scalars.SetValue(i, c)
> color_scalars.SetName("colors")
> 
> # some scales
> scales_scalars = vtk.vtkFloatArray()
> scales_scalars.SetNumberOfValues(scales.shape[0])
> for i, r in enumerate(scales):
> scales_scalars.SetValue(i, r)
> scales_scalars.SetName("scales")
> 
> # the original index
> index_scalars = vtk.vtkIntArray()
> index_scalars.SetNumberOfValues(n_atoms)
> for i in range(n_atoms):
> index_scalars.SetValue(i, i)
> index_scalars.SetName("index")
> 
> scalars = vtk.vtkFloatArray()
> scalars.SetNumberOfComponents(3)
> scalars.SetNumberOfTuples(scales_scalars.GetNumberOfTuples())
> scalars.CopyComponent(0, scales_scalars, 0)
> scalars.CopyComponent(1, color_scalars, 0)
> scalars.CopyComponent(2, index_scalars, 0)
> scalars.SetName("scalars")
> return scalars
> 
> ```
> 
> class MolecularViewer(QtWidgets.QWidget):  
> “”“This class implements a molecular viewer.  
> “””
> 
> ```
> def __init__ (self, parent):
> 
> super(MolecularViewer, self). __init__ (parent)
> 
> self._iren = QVTKRenderWindowInteractor(self)
> 
> self._renderer = vtk.vtkRenderer()
> 
> self._iren.GetRenderWindow().AddRenderer(self._renderer)
> 
> self._iren.GetRenderWindow().SetPosition((0, 0))
> 
> self._iren.GetInteractorStyle().SetCurrentStyleToTrackballCamera()
> 
> self._iren.Enable()
> 
> self._camera = vtk.vtkCamera()
> self._renderer.SetActiveCamera(self._camera)
> self._camera.SetFocalPoint(0, 0, 0)
> self._camera.SetPosition(0, 0, 20)
> 
> self._previously_picked_atom = None
> 
> self._iren.Initialize()
> 
> self._atoms = np.random.choice(list(CHEMICAL_ELEMENTS.keys()), NUMBER_OF_ATOMS).tolist()
> 
> self._atom_colours, self._lut = self.build_color_transfer_function()
> 
> self._atom_scales = np.array([CHEMICAL_ELEMENTS[at]['vdw_radius'] for at in self._atoms]).astype(np.float32)
> 
> scalars = ndarray_to_vtkarray(self._atom_colours, self._atom_scales, len(self._atoms))
> 
> self._polydata = vtk.vtkPolyData()
> self._polydata.GetPointData().SetScalars(scalars)
> 
> coordinates = np.random.uniform(-100, 100, (NUMBER_OF_ATOMS, 3))
> 
> self.set_coordinates(coordinates)
> 
> self._iren.AddObserver("LeftButtonPressEvent", self.on_pick)
> 
> @property
> def iren(self):
> return self._iren
> 
> @property
> def renderer(self):
> return self._renderer
> 
> def build_color_transfer_function(self):
> """Returns the colors and their associated transfer function
> """
> 
> lut = vtk.vtkColorTransferFunction()
> 
> for (idx, color) in RGB_COLOURS.values():
> lut.AddRGBPoint(idx, *color)
> 
> colours = []
> unic_colours = {}
> 
> color_string_list = [color_string_to_rgb(CHEMICAL_ELEMENTS[at]['color']) for at in self._atoms]
> 
> col_ids = len(RGB_COLOURS)
> 
> for col in color_string_list:
> tup_col = tuple(col)
> if not (tup_col in unic_colours.keys()):
> unic_colours[tup_col] = col_ids
> lut.AddRGBPoint(col_ids, *tup_col)
> colours.append(col_ids)
> col_ids += 1
> else:
> colours.append(unic_colours[tup_col])
> 
> return colours, lut
> 
> def build_scene(self):
> '''
> build a vtkPolyData object for a given frame of the trajectory
> '''
> 
> actor_list = []
> line_actor = None
> 
> line_mapper = vtk.vtkPolyDataMapper()
> if vtk.vtkVersion.GetVTKMajorVersion() < 6:
> line_mapper.SetInput(self._polydata)
> else:
> line_mapper.SetInputData(self._polydata)
> 
> line_mapper.SetLookupTable(self._lut)
> line_mapper.ScalarVisibilityOn()
> line_mapper.ColorByArrayComponent("scalars", 1)
> line_actor = vtk.vtkLODActor()
> line_actor.GetProperty().SetLineWidth(3)
> line_actor.SetMapper(line_mapper)
> actor_list.append(line_actor)
> 
> sphere = vtk.vtkSphereSource()
> sphere.SetCenter(0, 0, 0)
> sphere.SetRadius(0.2)
> sphere.SetThetaResolution(RESOLUTION)
> sphere.SetPhiResolution(RESOLUTION)
> glyph = vtk.vtkGlyph3D()
> glyph.SetInputData(self._polydata)
> 
> glyph.SetScaleModeToScaleByScalar()
> glyph.SetColorModeToColorByScalar()
> glyph.SetScaleFactor(1)
> glyph.SetSourceConnection(sphere.GetOutputPort())
> glyph.SetIndexModeToScalar()
> sphere_mapper = vtk.vtkPolyDataMapper()
> sphere_mapper.SetLookupTable(self._lut)
> sphere_mapper.SetScalarRange(self._polydata.GetScalarRange())
> sphere_mapper.SetInputConnection(glyph.GetOutputPort())
> sphere_mapper.ScalarVisibilityOn()
> sphere_mapper.ColorByArrayComponent("scalars", 1)
> ball_actor = vtk.vtkLODActor()
> ball_actor.SetMapper(sphere_mapper)
> ball_actor.GetProperty().SetAmbient(0.2)
> ball_actor.GetProperty().SetDiffuse(0.5)
> ball_actor.GetProperty().SetSpecular(0.3)
> ball_actor.SetNumberOfCloudPoints(30000)
> actor_list.append(ball_actor)
> self.glyph = glyph
> 
> self._picking_domain = ball_actor
> 
> assembly = vtk.vtkAssembly()
> for actor in actor_list:
> assembly.AddPart(actor)
> 
> return assembly
> 
> def clear_actors(self):
> """Clear the trajectory and the vtk scene.
> """
> 
> if not hasattr(self, "_actors"):
> return
> 
> self._actors.VisibilityOff()
> self._actors.ReleaseGraphicsResources(self._iren.GetRenderWindow())
> self._renderer.RemoveActor(self._actors)
> 
> del self._actors
> 
> def get_atom_index(self, pid):
> """Return the atom index from the vtk data point index.
> 
> Args:
> pid (int): the data point index
> """
> 
> _, _, idx = self.glyph.GetOutput().GetPointData().GetArray("scalars").GetTuple3(pid)
> 
> return int(idx)
> 
> def on_pick(self, obj, event=None):
> """Event handler when an atom is mouse-picked with the left mouse button
> """
> 
> # Get the picked position and retrieve the index of the atom that was picked from it
> pos = obj.GetEventPosition()
> 
> picker = vtk.vtkCellPicker()
> picker.SetTolerance(0.005)
> 
> picker.AddPickList(self._picking_domain)
> picker.PickFromListOn()
> picker.Pick(pos[0], pos[1], 0, self._renderer)
> pid = picker.GetPointId()
> if pid > 0:
> idx = self.get_atom_index(pid)
> self.on_pick_atom(idx)
> 
> def on_pick_atom(self, picked_atom):
> """Change the color of a selected atom
> """
> 
> # If an atom was previously picked, restore its scale and color
> if self._previously_picked_atom is not None:
> index, scale, color = self._previously_picked_atom
> self._atom_scales[index] = scale
> self._atom_colours[index] = color
> self._polydata.GetPointData().GetArray("scalars").SetTuple3(
> index, self._atom_scales[index], self._atom_colours[index], index)
> 
> # Save the scale and color of the picked atom
> self._previously_picked_atom = (
> picked_atom, self._atom_scales[picked_atom], self._atom_colours[picked_atom])
> 
> # Set its colors with the default value for atom selection and increase its size
> self._atom_colours[picked_atom] = RGB_COLOURS['selection'][0]
> self._atom_scales[picked_atom] *= 2
> 
> self._polydata.GetPointData().GetArray("scalars").SetTuple3(picked_atom, self._atom_scales[picked_atom], self._atom_colours[picked_atom], picked_atom)
> 
> self._polydata.Modified()
> 
> # self._iren.Render()
> 
> def set_coordinates(self, coords):
> '''
> Sets a new configuration
> 
> @param frame: the configuration number
> @type frame: integer
> '''
> 
> self.clear_actors()
> 
> points = vtk.vtkPoints()
> points.SetNumberOfPoints(len(self._atoms))
> for i, (x, y, z) in enumerate(coords):
> points.SetPoint(i, x, y, z)
> 
> self._polydata.SetPoints(points)
> 
> # Update the view.
> self.update_renderer()
> 
> def update_renderer(self):
> '''
> Update the renderer
> '''
> # deleting old frame
> self.clear_actors()
> 
> # creating new polydata
> self._actors = self.build_scene()
> 
> # adding polydata to renderer
> self._renderer.AddActor(self._actors)
> 
> # rendering
> self._iren.Render()
> 
> ```
> 
> class MainWindow(QtWidgets.QMainWindow):  
> “”“This class implements the main window of the application.  
> “””
> 
> ```
> def __init__ (self, parent=None):
> super(MainWindow, self). __init__ (parent)
> 
> self.init_ui()
> 
> def build_layout(self):
> """Build the layout of the main window.
> """
> 
> self._vl = QtWidgets.QVBoxLayout()
> self._vl.addWidget(self._molecular_viewer.iren)
> 
> self._main_frame.setLayout(self._vl)
> 
> def build_widgets(self):
> """Build the widgets of the main window.
> """
> 
> self._main_frame = QtWidgets.QFrame(self)
> 
> self._molecular_viewer = MolecularViewer(self._main_frame)
> self._molecular_viewer.renderer.ResetCamera()
> self._molecular_viewer.iren.Initialize()
> self._molecular_viewer.iren.Start()
> 
> self.setCentralWidget(self._main_frame)
> 
> self.setGeometry(0, 0, 800, 800)
> 
> self.show()
> 
> def init_ui(self):
> """Set the widgets of the main window
> """
> 
> self.build_widgets()
> self.build_layout()
> 
> ```
> 
> app = QtWidgets.QApplication(sys.argv)
> 
> window = MainWindow()
> 
> sys.exit(app.exec\_())
