Berk Geveci     About     Archive     Feed

A VTK pipeline primer (part 3)

In previous blogs (1, 2), I started discussing how the VTK pipeline functions. We covered the basics and how RequestInformation works. In this article, we will discover how RequestUpdateExtent and RequestData work. Once you have a good understanding of these 3 passes, you can develop all kinds of algorithms, ranging from very basic to sophisticated.

RequestUpdateExtent

This pass is where algorithms can make requests for the upstream pipeline to fulfill. Its name originates from the first use case for this pass: requesting a subset of logical extent from an image source (aka update extent). In the current VTK, this pass is used for requesting many other things including time steps, partitions (pieces), ghost levels etc. Let's dig into an example. First let's create a request key with the following.

requestKey = keys.MakeKey(keys.IntegerRequestKey, "a request", "my module")

Then let's make a request using this key (at the end of the file):

f.UpdateInformation()
outInfo = f.GetOutputInformation(0)
outInfo.Set(requestKey, 0)
f.PropagateUpdateExtent()

Let's also change the filter and the source to print out the keys in the information objects during RequestUpdateExtent:

class MySource(VTKPythonAlgorithmBase):
    def RequestUpdateExtent(self, request, inInfo, outInfo):
        print "MySource RequestUpdateExtent:"
        print outInfo.GetInformationObject(0)
        return 1

class MyFilter(VTKPythonAlgorithmBase):
    def RequestUpdateExtent(self, request, inInfo, outInfo):
        print "MyFilter RequestUpdateExtent:"
        print outInfo.GetInformationObject(0)
        return 1

Now when we run our example, here is the output:

MyFilter RequestUpdateExtent:
vtkInformation (0x7fae09e73650)
  ...
  a request: 0

MySource RequestUpdateExtent:
vtkInformation (0x7fae09e732d0)
  ...
  a request: 0

So far, we let the pipeline simply copy upstream the a request key. However, it is sometimes necessary for a filter to modify a request value during the RequestUpdateExtent pass. For example, a filter may need a layer of ghost cells to produce piece-independent results and may add a ghost level to the request. Let's change MyFilter's RequestUpdateExtent to increment the value of a request.

class MyFilter(VTKPythonAlgorithmBase):
    def RequestUpdateExtent(self, request, inInfo, outInfo):
        print "MyFilter RequestUpdateExtent:"
        print outInfo.GetInformationObject(0)
        areq = outInfo.GetInformationObject(0).Get(requestKey)
        inInfo[0].GetInformationObject(0).Set(requestKey, areq + 1)
        return 1

Now the output will look as follows:

MyFilter RequestUpdateExtent:
vtkInformation (0x7fae09e73650)
  ...
  a request: 0

MySource RequestUpdateExtent:
vtkInformation (0x7fae09e732d0)
  ...
  a request: 1

Here is a graphical representation of what is going on.

meta-data2

Simple huh? A few things to note:

We are now done covering all of the meta-data passes. In summary:

RequestData

Hopefully, this section is trivial to most readers. In RequestData, data, originally produced by sources, is transformed by filters as it propagates downstream. This is very similar to RequestInformation, the main difference is that "heavy" data is processed in RequestData as opposed to meta (light) data in RequestInformation.

In this pass, algorithms deal with the vtkDataObject.DATA_OBJECT() key, which is preset by the pipeline or the algorithm (more on this some other time). Let's modify our example to do some work in RequestData:

1  class MySource(VTKPythonAlgorithmBase):
2      def RequestData(self, request, inInfo, outInfo):
3          print "MySource RequestData:"
4          outInfo0 = outInfo.GetInformationObject(0)
5          areq = outInfo0.Get(requestKey)
6          s = vtk.vtkSphereSource()
7          s.SetRadius(areq)
8          s.Update()
9          output = outInfo0.Get(vtk.vtkDataObject.DATA_OBJECT())
10         output.ShallowCopy(s.GetOutput())
11         print output
12         return 1
13 
14 class MyFilter(VTKPythonAlgorithmBase):
15     def RequestData(self, request, inInfo, outInfo):
16         print "MyFilter RequestData:"
17         inInfo0 = inInfo[0].GetInformationObject(0)
18         outInfo0 = outInfo.GetInformationObject(0)
19         input = inInfo0.Get(vtk.vtkDataObject.DATA_OBJECT())
20         output = outInfo0.Get(vtk.vtkDataObject.DATA_OBJECT())
21         sh = vtk.vtkShrinkPolyData()
22         sh.SetInputData(input)
23         sh.Update()
24         output.ShallowCopy(sh.GetOutput())
25         print output
26         return 1

The source and the filter extract their output from the output information on lines 9 and 20 respectively. Note that these objects are guaranteed to exist and be of type vtkPolyData by the pipeline code. The type is determined by the constructor code, which looks like this:

class MyFilter(VTKPythonAlgorithmBase):
    def __init__(self):
        VTKPythonAlgorithmBase.__init__(self,
            nInputPorts=1, inputType='vtkPolyData',
            nOutputPorts=1, outputType='vtkPolyData')

The source uses the request object (line 5) to set the radius of the sphere that it produces (line 7). This is a good example of how source and filters can use the request objects in their RequestData methods (for example to read a particular time step or a spatial subset).

This is it! Hopefully, now you have a good understanding of the inner working of the VTK pipeline. We'll get to put this knowledge to use in upcoming blogs. You can find the full example on this page. I also recommend taking a look at this blog as it demonstrates how these concepts are used in developing an HDF5 reader.