3.4. Basic Filter Implementation
Chapter 3.3 (Simple Worklets) introduced the concept of a worklet and demonstrated how to create and run one to execute an algorithm on a device. Although worklets provide a powerful mechanism for designing heavily threaded visualization algorithms, invoking them requires quite a bit of knowledge of the workings of VTK‑m. Instead, most users execute algorithms in VTK‑m using filters. Thus, to expose algorithms implemented with worklets to general users, we need to implement a filter to encapsulate the worklets. In this chapter we will create a filter that encapsulates the worklet algorithm presented in Chapter 3.3 (Simple Worklets), which converted the units of a pressure field from pounds per square inch (psi) to Newtons per square meter (\(\mathrm{N}/\mathrm{m}^2\)).
Filters in VTK‑m are implemented by deriving vtkm::filter::Filter
.
The following example shows the declaration of our pressure unit conversion filter.
VTK‑m filters are divided into libraries.
In this example, we are assuming this filter is being compiled in a library named vtkm::filter::unit_conversion
.
By convention, the source files would be placed in a directory named vtkm/filter/unit_conversion
.
1namespace vtkm
2{
3namespace filter
4{
5namespace unit_conversion
6{
7
8class VTKM_FILTER_UNIT_CONVERSION_EXPORT PoundsPerSquareInchToNewtonsPerSquareMeterFilter
9 : public vtkm::filter::Filter
10{
11public:
12 VTKM_CONT PoundsPerSquareInchToNewtonsPerSquareMeterFilter();
13
14 VTKM_CONT vtkm::cont::DataSet DoExecute(const vtkm::cont::DataSet& inDataSet) override;
15};
16
17}
18}
19} // namespace vtkm::filter::unit_conversion
It is typical for a filter to have a constructor to set up its initial state.
A filter will also override the vtkm::filter::Filter::DoExecute()
method.
The vtkm::filter::Filter::DoExecute()
method takes a vtkm::cont::DataSet
as input and likewise returns a vtkm::cont::DataSet
containing the results of the filter operation.
Note that the declaration of the PoundsPerSquareInchToNewtonsPerSquareMeterFilter
contains the export macro VTKM_FILTER_UNIT_CONVERSION_EXPORT
.
This is a macro generated by CMake to handle the appropriate modifies for exporting a class from a library.
Remember that this code is to be placed in a library named vtkm::filter::unit_conversion
.
For this library, CMake creates a header file named vtkm/filter/unit_conversion.h
that declares macros like VTKM_FILTER_UNIT_CONVERSION_EXPORT
.
Did You Know?
A filter can also override the vtkm::filter::Filter::DoExecutePartitions()
, which operates on a vtkm::cont::PartitionedDataSet
.
If vtkm::filter::Filter::DoExecutePartitions()
is not overridden, then the filter will call vtkm::filter::Filter::DoExecute()
on each of the partitions and build a new vtkm::cont::PartitionedDataSet
with the outputs.
-
virtual vtkm::cont::PartitionedDataSet vtkm::filter::Filter::DoExecutePartitions(const vtkm::cont::PartitionedDataSet &inData)
Once the filter class is declared in the .h
file, the filter implementation is by convention given in a separate .cxx
file.
Given the definition of our filter in Example 3.20, we will need to provide the implementation for the constructor and the vtkm::filter::Filter::DoExecute()
method.
The constructor is quite simple.
It initializes the name of the output field name, which is managed by the superclass.
1VTKM_CONT PoundsPerSquareInchToNewtonsPerSquareMeterFilter::
2 PoundsPerSquareInchToNewtonsPerSquareMeterFilter()
3{
4 this->SetOutputFieldName("");
5}
In this case, we are setting the output field name to the empty string. This is not to mean that the default name of the output field should be the empty string, which is not a good idea. Rather, as we will see later, we will use the empty string to flag an output name that should be derived from the input name.
The meat of the filter implementation is located in the vtkm::filter::Filter::DoExecute()
method.
1VTKM_CONT vtkm::cont::DataSet
2PoundsPerSquareInchToNewtonsPerSquareMeterFilter::DoExecute(
3 const vtkm::cont::DataSet& inDataSet)
4{
5 vtkm::cont::Field inField = this->GetFieldFromDataSet(inDataSet);
6
7 vtkm::cont::UnknownArrayHandle outArray;
8
9 auto resolveType = [&](const auto& inputArray) {
10 // use std::decay to remove const ref from the decltype of concrete.
11 using T = typename std::decay_t<decltype(inputArray)>::ValueType;
12 vtkm::cont::ArrayHandle<T> result;
13 this->Invoke(
14 PoundsPerSquareInchToNewtonsPerSquareMeterWorklet{}, inputArray, result);
15 outArray = result;
16 };
17
18 this->CastAndCallScalarField(inField, resolveType);
19
20 std::string outFieldName = this->GetOutputFieldName();
21 if (outFieldName == "")
22 {
23 outFieldName = inField.GetName() + "_N/m^2";
24 }
25
26 return this->CreateResultField(
27 inDataSet, outFieldName, inField.GetAssociation(), outArray);
28}
The single argument to vtkm::filter::Filter::DoExecute()
is a vtkm::cont::DataSet
containing the data to operate on, and vtkm::filter::Filter::DoExecute()
returns a derived vtkm::cont::DataSet
.
The filter must pull the appropriate information out of the input vtkm::cont::DataSet
to operate on.
This simple algorithm just operates on a single field array of the data.
The vtkm::filter::Filter
base class provides several methods, documented in Section 2.6.2.1 (Input Fields), to allow filter users to select the active field to operate on.
The filter implementation can get the appropriate field to operate on using the vtkm::filter::Filter::GetFieldFromDataSet()
method as shown in Example 3.22, line 5.
-
inline const vtkm::cont::Field &vtkm::filter::Filter::GetFieldFromDataSet(const vtkm::cont::DataSet &input) const
Retrieve an input field from a
vtkm::cont::DataSet
object.When a filter operates on fields, it should use this method to get the input fields that the use has selected with
SetActiveField()
and related methods.
-
inline const vtkm::cont::Field &vtkm::filter::Filter::GetFieldFromDataSet(vtkm::IdComponent index, const vtkm::cont::DataSet &input) const
Retrieve an input field from a
vtkm::cont::DataSet
object.When a filter operates on fields, it should use this method to get the input fields that the use has selected with
SetActiveField()
and related methods.
One of the challenges with writing filters is determining the actual types the algorithm is operating on.
The vtkm::cont::Field
object pulled from the input vtkm::cont::DataSet
contains a vtkm::cont::ArrayHandle
(see Chapter 3.2 (Basic Array Handles)), but you do not know what the template parameters of the vtkm::cont::ArrayHandle
are.
There are numerous ways to extract an array of an unknown type out of a vtkm::cont::ArrayHandle
(many of which will be explored later in Chapter ref{chap:UnknownArrayHandle}), but the vtkm::filter::Filter
contains some convenience functions to simplify this.
In particular, this filter operates specifically on scalar fields.
For this purpose, vtkm::filter::Filter
provides the vtkm::filter::Filter::CastAndCallScalarField()
helper method.
The first argument to vtkm::filter::Filter::CastAndCallScalarField()
is the field containing the data to operate on.
The second argument is a functor that will operate on the array once it is identified.
vtkm::filter::Filter::CastAndCallScalarField()
will pull a vtkm::cont::ArrayHandle
out of the field and call the provided functor with that object.
vtkm::filter::Filter::CastAndCallScalarField()
is called in Example 3.22, line 18.
-
template<typename Functor, typename ...Args>
inline void vtkm::filter::Filter::CastAndCallScalarField(const vtkm::cont::UnknownArrayHandle &fieldArray, Functor &&functor, Args&&... args) const Convenience method to get the array from a filter’s input scalar field.
A field filter typically gets its input fields using the internal
GetFieldFromDataSet
. To use this field in a worklet, it eventually needs to be converted to anvtkm::cont::ArrayHandle
. If the input field is limited to be a scalar field, then this method provides a convenient way to determine the correct array type. Like otherCastAndCall
methods, it takes as input avtkm::cont::Field
(orvtkm::cont::UnknownArrayHandle
) and a function/functor to call with the appropriatevtkm::cont::ArrayHandle
type.
-
template<typename Functor, typename ...Args>
inline void vtkm::filter::Filter::CastAndCallScalarField(const vtkm::cont::Field &field, Functor &&functor, Args&&... args) const Convenience method to get the array from a filter’s input scalar field.
A field filter typically gets its input fields using the internal
GetFieldFromDataSet
. To use this field in a worklet, it eventually needs to be converted to anvtkm::cont::ArrayHandle
. If the input field is limited to be a scalar field, then this method provides a convenient way to determine the correct array type. Like otherCastAndCall
methods, it takes as input avtkm::cont::Field
(orvtkm::cont::UnknownArrayHandle
) and a function/functor to call with the appropriatevtkm::cont::ArrayHandle
type.
Did You Know?
If your filter requires a field containing vtkm::Vec
valuess of a particular size (e.g. 3), you can use the convenience method vtkm::filter::Filter::CastAndCallVecField()
.
vtkm::filter::Filter::CastAndCallVecField()
works similarly to vtkm::filter::Filter::CastAndCallScalarField()
except that it takes a template parameter specifying the size of the vtkm::Vec
.
For example, vtkm::filter::Filter::CastAndCallVecField<3>(inField, functor);
.
As previously stated, one of the arguments to vtkm::filter::Filter::CastAndCallScalarField()
is a functor that contains the routine to call with the found vtkm::cont::ArrayHandle
.
A functor can be created as its own class
or struct
, but a more convenient method is to use a C++ lambda.
A lambda is an unnamed function defined inline with the code.
The lambda in Example 3.22 starts on line 9.
Apart from being more convenient than creating a named class, lambda functions offer another important feature.
Lambda functions can “capture” variables in the current scope.
They can therefore access things like local variables and the this
reference to the method’s class (even accessing private members).
The callback to the lambda function in Example 3.22 first creates an output vtkm::cont::ArrayHandle
of a compatible type (line 12), then invokes the worklet that computes the derived field (line 13), and finally captures the resulting array.
Note that the vtkm::filter::Filter
base class provides a vtkm::filter::Filter::Invoke()
member that can be used to invoke the worklet.
(See Section 3.3.5 (Invoking a Worklet) for information on invoking a worklet.)
Recall that the worklet created in Chapter 3.3 (Simple Worklets) takes two parameters: an input array and an output array, which are shown in this invocation.
With the output data created, the filter has to build the output structure to return.
All implementations of vtkm::filter::Filter::DoExecute()
must return a vtkm::cont::DataSet
, and for a simple field filter like this we want to return the same vtkm::cont::DataSet
as the input with the output field added.
The output field needs a name, and we get the appropriate name from the superclass (Example 3.22, line 20).
However, we would like a special case where if the user does not specify an output field name we construct one based on the input field name.
Recall from Example 3.21 that by default we set the output field name to the empty string.
Thus, our filter checks for this empty string, and if it is encountered, it builds a field name by appending “_N/M^2” to it.
Finally, our filter constructs the output vtkm::cont::DataSet
using one of the vtkm::filter::Filter::CreateResult()
member functions (Example 3.22, line 26).
In this particular case, the filter uses vtkm::filter::Filter::CreateResultField()
, which constructs a vtkm::cont::DataSet
with the same structure as the input and adds the computed filter.
-
vtkm::cont::DataSet vtkm::filter::Filter::CreateResult(const vtkm::cont::DataSet &inDataSet) const
Create the output data set for
DoExecute
.This form of
CreateResult
will create an output data set with the same cell structure and coordinate system as the input and pass all fields (as requested by theFilter
state).- Parameters:
inDataSet – [in] The input data set being modified (usually the one passed into
DoExecute
). The returnedDataSet
is filled with the cell set, coordinate system, and fields ofinDataSet
(as selected by theFieldsToPass
state of the filter).
-
vtkm::cont::PartitionedDataSet vtkm::filter::Filter::CreateResult(const vtkm::cont::PartitionedDataSet &input, const vtkm::cont::PartitionedDataSet &resultPartitions) const
Create the output data set for
DoExecute
.This form of
CreateResult
will create an output PartitionedDataSet with the same partitions and pass all PartitionedDataSet fields (as requested by theFilter
state).- Parameters:
input – [in] The input data set being modified (usually the one passed into
DoExecute
).resultPartitions – [in] The output data created by the filter. Fields from the input are passed onto the return result partition as requested by the
Filter
state.
-
template<typename FieldMapper>
inline vtkm::cont::PartitionedDataSet vtkm::filter::Filter::CreateResult(const vtkm::cont::PartitionedDataSet &input, const vtkm::cont::PartitionedDataSet &resultPartitions, FieldMapper &&fieldMapper) const Create the output data set for
DoExecute
.This form of
CreateResult
will create an output PartitionedDataSet with the same partitions and pass all PartitionedDataSet fields (as requested by theFilter
state).- Parameters:
input – [in] The input data set being modified (usually the one passed into
DoExecute
).resultPartitions – [in] The output data created by the filter. Fields from the input are passed onto the return result partition as requested by the
Filter
state.fieldMapper – [in] A function or functor that takes a
PartitionedDataSet
as its first argument and aField
as its second argument. ThePartitionedDataSet
is the data being created and will eventually be returned byCreateResult
. TheField
comes frominput
.
-
template<typename FieldMapper>
inline vtkm::cont::DataSet vtkm::filter::Filter::CreateResult(const vtkm::cont::DataSet &inDataSet, const vtkm::cont::UnknownCellSet &resultCellSet, FieldMapper &&fieldMapper) const Create the output data set for
DoExecute
.This form of
CreateResult
will create an output data set with the givenCellSet
. You must also provide a field mapper function, which is a function that takes the outputDataSet
being created and aField
from the input and then applies any necessary transformations to the field array and adds it to theDataSet
.- Parameters:
inDataSet – [in] The input data set being modified (usually the one passed into
DoExecute
). The returnedDataSet
is filled with fields ofinDataSet
(as selected by theFieldsToPass
state of the filter).resultCellSet – [in] The
CellSet
of the output will be set to this.fieldMapper – [in] A function or functor that takes a
DataSet
as its first argument and aField
as its second argument. TheDataSet
is the data being created and will eventually be returned byCreateResult
. TheField
comes frominDataSet
. The function should map theField
to matchresultCellSet
and then add the resulting field to theDataSet
. If the mapping is not possible, then the function should do nothing.
-
vtkm::cont::DataSet vtkm::filter::Filter::CreateResultField(const vtkm::cont::DataSet &inDataSet, const vtkm::cont::Field &resultField) const
Create the output data set for
DoExecute
This form of
CreateResult
will create an output data set with the same cell structure and coordinate system as the input and pass all fields (as requested by theFilter
state). Additionally, it will add the provided field to the result.- Parameters:
inDataSet – [in] The input data set being modified (usually the one passed into
DoExecute
). The returnedDataSet
is filled with fields ofinDataSet
(as selected by theFieldsToPass
state of the filter).resultField – [in] A
Field
that is added to the returnedDataSet
.
-
inline vtkm::cont::DataSet vtkm::filter::Filter::CreateResultField(const vtkm::cont::DataSet &inDataSet, const std::string &resultFieldName, vtkm::cont::Field::Association resultFieldAssociation, const vtkm::cont::UnknownArrayHandle &resultFieldArray) const
Create the output data set for
DoExecute
This form of
CreateResult
will create an output data set with the same cell structure and coordinate system as the input and pass all fields (as requested by theFilter
state). Additionally, it will add a field matching the provided specifications to the result.- Parameters:
inDataSet – [in] The input data set being modified (usually the one passed into
DoExecute
). The returnedDataSet
is filled with fields ofinDataSet
(as selected by theFieldsToPass
state of the filter).resultFieldName – [in] The name of the field added to the returned
DataSet
.resultFieldAssociation – [in] The association of the field (e.g. point or cell) added to the returned
DataSet
.resultFieldArray – [in] An array containing the data for the field added to the returned
DataSet
.
-
inline vtkm::cont::DataSet vtkm::filter::Filter::CreateResultFieldPoint(const vtkm::cont::DataSet &inDataSet, const std::string &resultFieldName, const vtkm::cont::UnknownArrayHandle &resultFieldArray) const
Create the output data set for
DoExecute
This form of
CreateResult
will create an output data set with the same cell structure and coordinate system as the input and pass all fields (as requested by theFilter
state). Additionally, it will add a point field matching the provided specifications to the result.- Parameters:
inDataSet – [in] The input data set being modified (usually the one passed into
DoExecute
). The returnedDataSet
is filled with fields ofinDataSet
(as selected by theFieldsToPass
state of the filter).resultFieldName – [in] The name of the field added to the returned
DataSet
.resultFieldArray – [in] An array containing the data for the field added to the returned
DataSet
.
-
inline vtkm::cont::DataSet vtkm::filter::Filter::CreateResultFieldCell(const vtkm::cont::DataSet &inDataSet, const std::string &resultFieldName, const vtkm::cont::UnknownArrayHandle &resultFieldArray) const
Create the output data set for
DoExecute
This form of
CreateResult
will create an output data set with the same cell structure and coordinate system as the input and pass all fields (as requested by theFilter
state). Additionally, it will add a cell field matching the provided specifications to the result.- Parameters:
inDataSet – [in] The input data set being modified (usually the one passed into
DoExecute
). The returnedDataSet
is filled with fields ofinDataSet
(as selected by theFieldsToPass
state of the filter).resultFieldName – [in] The name of the field added to the returned
DataSet
.resultFieldArray – [in] An array containing the data for the field added to the returned
DataSet
.
-
template<typename FieldMapper>
inline vtkm::cont::DataSet vtkm::filter::Filter::CreateResultCoordinateSystem(const vtkm::cont::DataSet &inDataSet, const vtkm::cont::UnknownCellSet &resultCellSet, const vtkm::cont::CoordinateSystem &resultCoordSystem, FieldMapper &&fieldMapper) const Create the output data set for
DoExecute
.This form of
CreateResult
will create an output data set with the givenCellSet
andCoordinateSystem
. You must also provide a field mapper function, which is a function that takes the outputDataSet
being created and aField
from the input and then applies any necessary transformations to the field array and adds it to theDataSet
.- Parameters:
inDataSet – [in] The input data set being modified (usually the one passed into
DoExecute
). The returnedDataSet
is filled with fields ofinDataSet
(as selected by theFieldsToPass
state of the filter).resultCellSet – [in] The
CellSet
of the output will be set to this.resultCoordSystem – [in] This
CoordinateSystem
will be added to the output.fieldMapper – [in] A function or functor that takes a
DataSet
as its first argument and aField
as its second argument. TheDataSet
is the data being created and will eventually be returned byCreateResult
. TheField
comes frominDataSet
. The function should map theField
to matchresultCellSet
and then add the resulting field to theDataSet
. If the mapping is not possible, then the function should do nothing.
-
template<typename FieldMapper>
inline vtkm::cont::DataSet vtkm::filter::Filter::CreateResultCoordinateSystem(const vtkm::cont::DataSet &inDataSet, const vtkm::cont::UnknownCellSet &resultCellSet, const std::string &coordsName, const vtkm::cont::UnknownArrayHandle &coordsData, FieldMapper &&fieldMapper) const Create the output data set for
DoExecute
.This form of
CreateResult
will create an output data set with the givenCellSet
andCoordinateSystem
. You must also provide a field mapper function, which is a function that takes the outputDataSet
being created and aField
from the input and then applies any necessary transformations to the field array and adds it to theDataSet
.- Parameters:
inDataSet – [in] The input data set being modified (usually the one passed into
DoExecute
). The returnedDataSet
is filled with fields ofinDataSet
(as selected by theFieldsToPass
state of the filter).resultCellSet – [in] The
CellSet
of the output will be set to this.coordsName – [in] The name of the coordinate system to be added to the output.
coordsData – [in] The array containing the coordinates of the points.
fieldMapper – [in] A function or functor that takes a
DataSet
as its first argument and aField
as its second argument. TheDataSet
is the data being created and will eventually be returned byCreateResult
. TheField
comes frominDataSet
. The function should map theField
to matchresultCellSet
and then add the resulting field to theDataSet
. If the mapping is not possible, then the function should do nothing.
Common Errors
The vtkm::filter::Filter::CreateResult()
methods do more than just construct a new vtkm::cont::DataSet
.
They also set up the structure of the data and pass fields as specified by the state of the filter object.
Thus, implementations of vtkm::filter::Filter::DoExecute()
should always return a vtkm::cont::DataSet
that is created with vtkm::filter::Filter::CreateResult()
or a similarly named method in the base filter class.
This chapter has just provided a brief introduction to creating filters. There are several more filter superclasses to help express algorithms of different types. After some more worklet concepts to implement more complex algorithms are introduced in Part 4 (Advanced Development), we will see a more complete documentation of the types of filters in Chapter 4.4 (Extended Filter Implementations).