mirror of
https://gitlab.kitware.com/vtk/vtk-m
synced 2024-10-05 01:49:02 +00:00
136 lines
12 KiB
ReStructuredText
136 lines
12 KiB
ReStructuredText
==============================
|
|
Basic Filter Implementation
|
|
==============================
|
|
|
|
.. index:: filter; implementation
|
|
|
|
:chapref:`simple-worklets: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 |VTKm|.
|
|
Instead, most users execute algorithms in |VTKm| 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 :chapref:`simple-worklets:Simple Worklets`, which converted the units of a pressure field from pounds per square inch (psi) to Newtons per square meter (:math:`\mathrm{N}/\mathrm{m}^2`).
|
|
|
|
Filters in |VTKm| are implemented by deriving :class:`vtkm::filter::Filter`.
|
|
|
|
|
|
.. Comment this out. Too much duplicate documentation makes it confusing.
|
|
doxygenclass:: vtkm::filter::Filter
|
|
|
|
The following example shows the declaration of our pressure unit conversion filter.
|
|
|VTKm| 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``.
|
|
|
|
.. load-example:: SimpleField
|
|
:file: GuideExampleSimpleAlgorithm.cxx
|
|
:caption: Header declaration for a simple filter.
|
|
|
|
It is typical for a filter to have a constructor to set up its initial state.
|
|
A filter will also override the :func:`vtkm::filter::Filter::DoExecute` method.
|
|
The :func:`vtkm::filter::Filter::DoExecute` method takes a :class:`vtkm::cont::DataSet` as input and likewise returns a :class:`vtkm::cont::DataSet` containing the results of the filter operation.
|
|
|
|
.. doxygenfunction:: vtkm::filter::Filter::DoExecute
|
|
|
|
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``.
|
|
|
|
.. didyouknow::
|
|
A filter can also override the :func:`vtkm::filter::Filter::DoExecutePartitions`, which operates on a :class:`vtkm::cont::PartitionedDataSet`.
|
|
If :func:`vtkm::filter::Filter::DoExecutePartitions` is not overridden, then the filter will call :func:`vtkm::filter::Filter::DoExecute` on each of the partitions and build a new :class:`vtkm::cont::PartitionedDataSet` with the outputs.
|
|
|
|
.. doxygenfunction:: vtkm::filter::Filter::DoExecutePartitions
|
|
|
|
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 :numref:`ex:SimpleField`, we will need to provide the implementation for the constructor and the :func:`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.
|
|
|
|
.. load-example:: SimpleFieldConstructor
|
|
:file: GuideExampleSimpleAlgorithm.cxx
|
|
:caption: Constructor for a simple filter.
|
|
|
|
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 :func:`vtkm::filter::Filter::DoExecute` method.
|
|
|
|
.. load-example:: SimpleFieldDoExecute
|
|
:file: GuideExampleSimpleAlgorithm.cxx
|
|
:caption: Implementation of ``DoExecute`` for a simple filter.
|
|
|
|
The single argument to :func:`vtkm::filter::Filter::DoExecute` is a :class:`vtkm::cont::DataSet` containing the data to operate on, and :func:`vtkm::filter::Filter::DoExecute` returns a derived :class:`vtkm::cont::DataSet`.
|
|
The filter must pull the appropriate information out of the input :class:`vtkm::cont::DataSet` to operate on.
|
|
This simple algorithm just operates on a single field array of the data.
|
|
The :class:`vtkm::filter::Filter` base class provides several methods, documented in :secref:`running-filters: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 :func:`vtkm::filter::Filter::GetFieldFromDataSet` method as shown in :exlineref:`ex:SimpleFieldDoExecute:InputField`.
|
|
|
|
.. doxygenfunction:: vtkm::filter::Filter::GetFieldFromDataSet(const vtkm::cont::DataSet&) const
|
|
.. doxygenfunction:: vtkm::filter::Filter::GetFieldFromDataSet(vtkm::IdComponent, const vtkm::cont::DataSet&) const
|
|
|
|
One of the challenges with writing filters is determining the actual types the algorithm is operating on.
|
|
The :class:`vtkm::cont::Field` object pulled from the input :class:`vtkm::cont::DataSet` contains a :class:`vtkm::cont::ArrayHandle` (see :chapref:`basic-array-handles:Basic Array Handles`), but you do not know what the template parameters of the :class:`vtkm::cont::ArrayHandle` are.
|
|
There are numerous ways to extract an array of an unknown type out of a :class:`vtkm::cont::ArrayHandle` (many of which will be explored later in Chapter \ref{chap:UnknownArrayHandle}), but the :class:`vtkm::filter::Filter` contains some convenience functions to simplify this.
|
|
|
|
.. todo:: Fix above reference to unknown array handle chapter.
|
|
|
|
In particular, this filter operates specifically on scalar fields.
|
|
For this purpose, :class:`vtkm::filter::Filter` provides the :func:`vtkm::filter::Filter::CastAndCallScalarField` helper method.
|
|
The first argument to :func:`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.
|
|
:func:`vtkm::filter::Filter::CastAndCallScalarField` will pull a :class:`vtkm::cont::ArrayHandle` out of the field and call the provided functor with that object.
|
|
:func:`vtkm::filter::Filter::CastAndCallScalarField` is called in :exlineref:`ex:SimpleFieldDoExecute:CastAndCall`.
|
|
|
|
.. doxygenfunction:: vtkm::filter::Filter::CastAndCallScalarField(const vtkm::cont::UnknownArrayHandle&, Functor&&, Args&&...) const
|
|
.. doxygenfunction:: vtkm::filter::Filter::CastAndCallScalarField(const vtkm::cont::Field&, Functor&&, Args&&...) const
|
|
|
|
.. didyouknow::
|
|
If your filter requires a field containing :type:`vtkm::Vec` valuess of a particular size (e.g. 3), you can use the convenience method :func:`vtkm::filter::Filter::CastAndCallVecField`.
|
|
:func:`vtkm::filter::Filter::CastAndCallVecField` works similarly to :func:`vtkm::filter::Filter::CastAndCallScalarField` except that it takes a template parameter specifying the size of the :type:`vtkm::Vec`.
|
|
For example, ``vtkm::filter::Filter::CastAndCallVecField<3>(inField, functor);``.
|
|
|
|
As previously stated, one of the arguments to :func:`vtkm::filter::Filter::CastAndCallScalarField` is a functor that contains the routine to call with the found :class:`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 :numref:`ex:SimpleFieldDoExecute` starts on :exlineref:`line {line}<ex:SimpleFieldDoExecute:Lambda>`.
|
|
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 :numref:`ex:SimpleFieldDoExecute` first creates an output :class:`vtkm::cont::ArrayHandle` of a compatible type (:exlineref:`line {line}<ex:SimpleFieldDoExecute:CreateOutputArray>`), then invokes the worklet that computes the derived field (:exlineref:`line {line}<ex:SimpleFieldDoExecute:Invoke>`), and finally captures the resulting array.
|
|
Note that the :class:`vtkm::filter::Filter` base class provides a :func:`vtkm::filter::Filter::Invoke` member that can be used to invoke the worklet.
|
|
(See :secref:`simple-worklets:Invoking a Worklet` for information on invoking a worklet.)
|
|
Recall that the worklet created in :chapref:`simple-worklets: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 :func:`vtkm::filter::Filter::DoExecute` must return a :class:`vtkm::cont::DataSet`, and for a simple field filter like this we want to return the same :class:`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 (:exlineref:`ex:SimpleFieldDoExecute:OutputName`).
|
|
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 :numref:`ex:SimpleFieldConstructor` 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 :class:`vtkm::cont::DataSet` using one of the :func:`vtkm::filter::Filter::CreateResult` member functions (:exlineref:`ex:SimpleFieldDoExecute:CreateResult`).
|
|
In this particular case, the filter uses :func:`vtkm::filter::Filter::CreateResultField`, which constructs a :class:`vtkm::cont::DataSet` with the same structure as the input and adds the computed filter.
|
|
|
|
.. doxygenfunction:: vtkm::filter::Filter::CreateResult(const vtkm::cont::DataSet&) const
|
|
.. doxygenfunction:: vtkm::filter::Filter::CreateResult(const vtkm::cont::PartitionedDataSet&, const vtkm::cont::PartitionedDataSet&) const
|
|
.. doxygenfunction:: vtkm::filter::Filter::CreateResult(const vtkm::cont::PartitionedDataSet&, const vtkm::cont::PartitionedDataSet&, FieldMapper&&) const
|
|
.. doxygenfunction:: vtkm::filter::Filter::CreateResult(const vtkm::cont::DataSet&, const vtkm::cont::UnknownCellSet&, FieldMapper&&) const
|
|
.. doxygenfunction:: vtkm::filter::Filter::CreateResultField(const vtkm::cont::DataSet&, const vtkm::cont::Field&) const
|
|
.. doxygenfunction:: vtkm::filter::Filter::CreateResultField(const vtkm::cont::DataSet&, const std::string&, vtkm::cont::Field::Association, const vtkm::cont::UnknownArrayHandle&) const
|
|
.. doxygenfunction:: vtkm::filter::Filter::CreateResultFieldPoint(const vtkm::cont::DataSet&, const std::string&, const vtkm::cont::UnknownArrayHandle&) const
|
|
.. doxygenfunction:: vtkm::filter::Filter::CreateResultFieldCell(const vtkm::cont::DataSet&, const std::string&, const vtkm::cont::UnknownArrayHandle&) const
|
|
.. doxygenfunction:: vtkm::filter::Filter::CreateResultCoordinateSystem(const vtkm::cont::DataSet&, const vtkm::cont::UnknownCellSet&, const vtkm::cont::CoordinateSystem&, FieldMapper&&) const
|
|
.. doxygenfunction:: vtkm::filter::Filter::CreateResultCoordinateSystem(const vtkm::cont::DataSet&, const vtkm::cont::UnknownCellSet&, const std::string&, const vtkm::cont::UnknownArrayHandle&, FieldMapper&&) const
|
|
|
|
.. commonerrors::
|
|
The :func:`vtkm::filter::Filter::CreateResult` methods do more than just construct a new :class:`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 :func:`vtkm::filter::Filter::DoExecute` should always return a :class:`vtkm::cont::DataSet` that is created with :func:`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 :partref:`part-advanced:Advanced Development`, we will see a more complete documentation of the types of filters in :chapref:`extended-filter-impl:Extended Filter Implementations`.
|