More Pythonic VTK wrapping

Thanks I was wondering about that.

I have written an example based on WarpCombustor, it is working with no problems, but is there a better way of adding pipelines to a vtkAppendPolyData filter:

...
    pl3d_output = pl3d.output.GetBlock(0)

    # Planes are specified using a imin,imax, jmin,jmax, kmin,kmax coordinate
    # specification. Min and max i,j,k values are clamped to 0 and maximum value.
    planes = ((pl3d_output >> vtkStructuredGridGeometryFilter(extent=[10, 10, 1, 100, 1, 100])).update().output,
              (pl3d_output >> vtkStructuredGridGeometryFilter(extent=[30, 30, 1, 100, 1, 100])).update().output,
              (pl3d_output >> vtkStructuredGridGeometryFilter(extent=[45, 45, 1, 100, 1, 100])).update().output)

    # We use an append filter because, in that way, we can do the warping, etc. just using a single pipeline and actor.
    append_filter = vtkAppendPolyData()
    for plane in planes:
        append_filter.AddInputData(plane)

    # Warp and generate the normals.
    p = (
            append_filter
            >> vtkWarpScalar(use_normal=True, normal=[1.0, 0.0, 0.0], scale_factor=2.5)
            >> vtkPolyDataNormals(feature_angle=60)
    ).update().output
...

If you want to execute the append filter in place, you can do:
appended = vtkAppendPolyData()(planes)

I am exploring ways of connecting multiple inputs to append. Probably something like this:

planes >> vtkAppendPolyData() # planes could be a data object, an algorithm or a pipeline

If we could connect multiple inputs like that it woild be great.

Your comment about executing the append filter in place works:

    p = (
            vtkAppendPolyData()(planes)
            >> vtkWarpScalar(use_normal=True, normal=[1.0, 0.0, 0.0], scale_factor=2.5)
            >> vtkPolyDataNormals(feature_angle=60)
    ).update().output

Thanks

I just pushed a change that supports multiple connections. These work:

[vtk.vtkSphereSource(), vtk.vtkSphereSource()] >> vtk.vtkAppendFilter()

a = vtk.vtkAppendFilter()
vtk.vtkSphereSource() >> a
vtk.vtkSphereSource() >> a

Note that you have to use a.RemoveAllInputConnections(0) to reset the append filter.

3 Likes

I have set up a MR for the VTK Examples using the new Python interface and I will update a test site so you can see the changes.
Please see:

  1. MR 327
  2. Web Pages test site

So far there are three examples there and I have added a Snippets section and a Features section. The next example that I’ll do will most likely be one where the pipline produces multiple outputs (hopefully!). There will be just a few examples spread across the main subfolders. As the Python interface evolves, I expect these examples to need editing, this is the reason for just having a few examples at this stage.

The Features section is something new. Here I am hoping to add little comments highlighting the features of the new interface. We can change the name to something else or, if you don´t like the name, we can change it.

It was easy to upgrade the code in the Snippets section, no particular issues there.

If you look at WarpCombustor you will see how multiple inputs work in the pipeline. One little thing to be aware of is that the snake case conversion of SetXYZFileName() becomes x_y_z_file_name not xyz_file_name. The other new example CheckVTKVersion may become useful in the future.

I’ll let everyone here know when I add more examples etc…

You might use None >> a as a shorthand for a.RemoveAllInputConnections(0).

Does [] >> a have similar effect to a.RemoveAllInputConnections(0)?

@jaswantp @dcthomp @berk.geveci I dropped this bit of code into WarpCombustor

    a = vtkAppendPolyData()
    (planes >> a)
    print(a.total_number_of_input_connections)
    (a.RemoveAllInputConnections(0))
    # (None >> a) # Gives: TypeError: unsupported operand type(s) for >>: NoneType and vtkAppendPolyData
    #([] >> a) # An empty list (or tuple is added) so the original input connections are not removed.
    print(a.total_number_of_input_connections)
    (planes >> a)
    print(a.total_number_of_input_connections)

So it seems a.RemoveAllInputConnections(0) is the only way to remove all the input connections.

Now None >> a and [] >> a both work now. None can also be used to clear any inputs, repeatable or not.

2 Likes

Nice, I’ll update the comment in VTK Examples (Features)

With respect to VTK Examples, I have added a few more examples and also removed the Features folder and replaced it with src/PythonicAPIComments.md as it it makes more sense to have all the comments in one file. If you need to add anything here please note that to prevent links to VTK classes surround the class with question marks e.g. … ?vtkMultiBlockPLOT3DReader?,…

Please have a look at: PythonicAPIComments, I hope this will become a useful document highlighting the API changes.

2 Likes

Overall I haven’t seen any difficulties doing the conversions to the new VTK Python API.
My impression is that the code is more compact and much easier to read.

However … I started doing the more complex examples and came across this when updating PBR_Skybox:

I can update this function to:

def read_cubemap(cubemap):
    cube_map = vtkTexture(cube_map=True, mipmap=True, interpolate=True)

    flipped_images = list()
    for fn in cubemap:
        reader_factory = vtkImageReader2Factory()
        img_reader = reader_factory.CreateImageReader2(str(fn))
        img_reader.file_name = str(fn)
        flip = vtkImageFlip(filtered_axis=1)
        img_reader >> flip
        flipped_images.append(flip)

    for i in range(0, len(flipped_images)):
        cube_map.SetInputConnection(i, flipped_images[i].GetOutputPort())
    return cube_map

Which works fine. However, thinking along the lines of vtkAppendFilter, is it possible to modify vtkTexture so that:

    flipped_images >> cube_map

works? To me SetInputConnection(i,...) is working in the same way as the input to vtkAppendFilter. Of course this assumes that the image files making up the cube map are already ordered as [right, left, top, bottom, front, back] or [+x, -x, +y, -y, +z, -z].

1 Like

I like the document but have a suggestion:

In some places, it is not clear what the type of some values are. For example

    pipeline_output = (a >> b >> c).update().output
    p1 = vtkSomeClass(input_data=pipeline_output[0]) >> e >> f
    p1 = vtkAnotherClass(input_data=pipeline_output[1]) >> g >> h

I assume pipeline_output is a tuple of vtkDataObject subclasses. Maybe adding a print statement with “possible output” would clarify?

    pipeline_output = (a >> b >> c).update().output
    print(pipeline_output)
    # Possible output:
    # (<vtkPolyData(0x1a0e500) at 0x7f7309ea26a0>, <vtkPolyData(0x1a0e2a0) at 0x7f7309ea2700>)
    p1 = vtkSomeClass(input_data=pipeline_output[0]) >> e >> f
    p1 = vtkAnotherClass(input_data=pipeline_output[1]) >> g >> h

flipped_images >> cube_map

I am confused. It doesn’t look like vtkTexture has multiple input ports. Does this code work if len(flipped_images) > 1?

len(flipped_images)is six in this case, and flipped_images >> cube_map does not give an error, however you do get this error on update: vtkOpenGLTexture.cxx:151 ERR| vtkOpenGLTexture (0x283e630): Cube Maps require 6 inputs.

I naively thought it would work in a manner analogous to that in vtkAppendFilter, but it seems six separate intput ports are required.

So probably not worth worrying about as you would have to warn that exactly six inputs are required in a specific order.

I guess this is why flipped_images >> cube_map doesn´t work in this case:

“SetInputConnection(self, input:vtkAlgorithmOutput) → None C++: virtual void SetInputConnection(vtkAlgorithmOutput *input)
Set the connection for the given input port index. Each input port of a filter has a specific purpose. A port may have zero or more connections and the required number is specified by each filter. Setting the connection with this method removes all other connections from the port. To add more than one connection use AddInputConnection().”

Maybe a type annotation if the type needs to be made clear?

    pipeline_output: tuple[vtkDataObject, vtkDataObject] = (a >> b >> c).update().output

Or maybe just giving the tuple elements good variable names is enough?

    first_output_data_object, second_output_data_object = (a >> b >> c).update().output

A bit of a mouthful though and with the example being just a general snippet, it’s not really possible to give them good names…

@dcthomp @estan How about:

"A pipeline can produce multiple outputs. Here p is a tuple of vtkDataObjects, for example: `p: tuple(vtkDataObject, …, vtkDataObject). Subsequent pipelines can access the indivudual elements of the tuple.

    p = (a >> b >> c).update().output
    p1 = vtkSomeClass(input_data=p[0]) >> e >> f
    p1 = vtkAnotherClass(input_data=p[1]) >> g >> h

"
In this way the example is not so verbose and hopefully clearer.

That sounds good. You do have 2 minor typos I noticed:

  • you’re missing a backquote (`) at the end of the tuple
  • “indivudual” should be “individual”

Will fix up, thanks