More Pythonic VTK wrapping

I know that we were almost done deciding on a method to update the pipeline and get the result, but there have been 2 good suggestions that we should consider:

  1. pipeline.update().data
  2. implementing the __call__ method so we can do: vtkContourFilter(contour_values=[100])(input)

My two cents on these: I like them both and I wouldn’t mind having both implemented. (1) is a shorter version of what we do in VTK, (2) would allow us to treat the filters as functors and pass them where functions/lambdas/callable objects are used such as map.

PS: I would suggest using the Update() method and having it return the algorithm rather than implementing a new Python method.

  • execute()
  • __call__()
  • update().output
0 voters

Ideally this would be “optional” and live under something like vtkmodules.util.networkx_support (a sibling of numpy_support. I don’t think import vtkmodules.vtkCommonCore should require networkx.

Absolutely. I don’t even intend to make numpy a required dependency.

Regarding execute()/__call__()/update() is there any appetite for something akin to the dask api style:

  • compute() for synchronous execution (blocking)
  • submit() for asynchronous execution (returning a Future)

What event loop is expected to be used to do the waiting on any async work ?

Guys don’t hijack this topic for new features :slight_smile: This is about an easy API not doing async computing. Feel free to start another topic for that.

Hi folks,

It took a bit but I now have an implementation of both update() and __call__. update() is equivalent to Update(). It does not take any input. It just updates the pipeline. I will add support for specifying things like pieces and time later. __call__ is for using an algorithm or a pipeline as a function. It will take either no arguments (sources), one data object (regular filters), or a sequence of data objects (repeatable inputs like append). Things that are possible:

aSource.update().output
(aSource >> aFilter).update().output # will return a tuple if aFilter has multiple outputs
aFilter(inpData)
(aFilter >> bFilter)(inpData)
aSource()
(aSource >> aFilter)()
app = vtk.vtkAppendPolyData()
app((poly1, poly2))

There are still a few things that I would like to implement such as specifying pieces etc. in update() and using sequences with >> for connecting pipelines to append etc. but I am going to focus on writing tests for what is there and getting it committed in the next few weeks and do those later.

1 Like

What is the value of

(aSource >> aFilter)()

? Is it equivalent to

(aSource >> aFilter).update().output

?

(aSource >> aFilter)() is equivalent to
(aSource >> aFilter).update().output so it does not have a lot of value. It was also easy to implement so I did it.
The main value of __call__ is that it can take a data object as argument so you can do this:
output = aFilter(inpData)
The equivalent of this with >> is
output = (inpData >> aFilter).update.output
which is not as concise AND it leaves aFilter in a state of pipeline connection. The () version lets you use pipelines and filters as functions (without hooking up the input AND splitting out the output through a shallow copy).

Hrm. While it is the most common case, does this mean we’ll have an ambiguity if we return some algorithm result as a tuple itself in the future (say we start using std::tuple in VTK)? Do the bindings know this that they can write it into the .pyi or __doc__ for the attribute/method? I know dealing with (vtkobject,) in the vastly common one-object case is annoying, but I do want to note this; I think mentioning the data in the generated docs is sufficient though.

Yeah, good point. I ran into this. The clip filter has two outputs, even though the second one is turned on with a flag. I was expecting () to return the data object, not a tuple, so my code was broken. I’ll do my best to document this. It is not always straightforward to document some of these methods (for example, I can’t find a way to document methods that have their slots such as tp_call (__call__).

Might need to change how we define objects. There is PyMethodDef which supports docstrings on slots. See this MR for work towards supporting the stable ABI (which would eventually use PyMethodDef).

1 Like

Wow, just look at this rewrite of CylinderExample

  1. I love being able to specify keyword arguments in the constructor!!!
  2. Note also that you can call other functions to set a keyword in the constructor, see ren = vtkRenderer... line 43.

Ideas for improvement welcome.

#!/usr/bin/env python3

# This simple example shows how to do basic rendering and pipeline creation.

# noinspection PyUnresolvedReferences
import vtkmodules.vtkInteractionStyle
# noinspection PyUnresolvedReferences
import vtkmodules.vtkRenderingOpenGL2
from vtkmodules.vtkCommonColor import vtkNamedColors
from vtkmodules.vtkFiltersSources import vtkCylinderSource
from vtkmodules.vtkRenderingCore import (
    vtkActor,
    vtkPolyDataMapper,
    vtkRenderWindow,
    vtkRenderWindowInteractor,
    vtkRenderer
)


def main():
    colors = vtkNamedColors()
    # Set the background color.
    bkg = map(lambda x: x / 255.0, [26, 51, 102, 255])
    colors.SetColor('BkgColor', *bkg)

    # This creates a polygonal cylinder model with eight circumferential facets.
    # We can initialize the properties of a wrapped VTK class by specifying keyword arguments in the constructor.
    cylinder = vtkCylinderSource(resolution=8)
    # We can also print the properties of a VTK object in a more pythonic way.
    print(
        f'Cylinder properties:\n   height: {cylinder.height}, radius: {cylinder.radius},'
        f' center: {cylinder.center} resolution: {cylinder.resolution} capping: {cylinder.capping == 1}')

    cm = vtkPolyDataMapper(input_connection=cylinder.output_port)

    ca = vtkActor(mapper=cm)
    ca.RotateX(30)
    ca.RotateY(-45)
    ca.GetProperty().SetColor(colors.GetColor3d('Tomato'))

    # Note the setting of the background by calling GetColor()
    ren = vtkRenderer(background=colors.GetColor3d('BkgColor'))
    ren.AddActor(ca)

    ren_win = vtkRenderWindow(size=[300, 300], window_name='CylinderExample')
    ren_win.AddRenderer(ren)

    iren = vtkRenderWindowInteractor()
    iren.SetRenderWindow(ren_win)

    iren.Initialize()
    ren.ResetCamera()
    ren.GetActiveCamera().Zoom(1.5)
    ren_win.Render()

    # Start the event loop.
    iren.Start()


if __name__ == '__main__':
    main()

2 Likes

Very cool!

By the way, you should also be able to do:

iren.render_window = ren_win

Other things like I’d like to be able to do to that code would be:

ren = vtkRenderer(background=colors.GetColor3d('BkgColor'),
                  actors=[ca])

and

ren_win = vtkRenderWindow(size=[300,300],
                          window_name='CylinderExample',
                          renderer=ren)

and

iren = vtkRenderWindowInteractor(render_window=ren_win)

Generates an: AttributeError: attribute 'actors' of 'vtkmodules.vtkRenderingCore.vtkRenderer' objects is not writable

I actually tried renderers= however this error is generated: AttributeError: attribute 'renderers' of 'vtkmodules.vtkRenderingCore.vtkRenderWindow' objects is not writable

Works but it is flagged as an unexpected argument in PyCharm.

Thanks for the comments.

@berk Done! It works. Thanks

Properties are only generated when there are Set and/or Get methods. If we want renderers to be a property, we need a SetRenderers method. Which is doable with std::vector argument now that we are allowing those. Same with actors.

2 Likes