A VTK pipeline primer (part 3)
26 Oct 2014In 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.
Simple huh? A few things to note:
Certain keys are copied upstream by the pipeline automatically during
RequestUpdateExtent
. These include all keys of typevtkInformationIntegerRequestKey
,UPDATE_EXTENT
,UPDATE_NUMBER_OF_GHOST_LEVELS
,UPDATE_PIECE_NUMBER
etc.Any automatic copying happens before the algorithm's
RequestUpdateExtent
is called so that it can overwrite anything done by the pipeline.
We are now done covering all of the meta-data passes. In summary:
In
RequestInformation
, the source produces meta-data, the pipeline copies these values downstream by default and the filters modify the meta-data as it propagates downstream.In
RequestUpdateExtent
, the sink (or the user at the end of the pipeline) makes requests and as these values propagate upstream, filters modify them to fit their needs.
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.