Add continued filter implementation chapter

This commit is contained in:
Kenneth Moreland 2024-02-10 12:05:47 -05:00
parent 7cc490d94a
commit 2fe05097eb
10 changed files with 775 additions and 30 deletions

@ -10,15 +10,13 @@ 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 one of the filter base classes provided in ``vtkm::filter``.
There are multiple base filter classes that we can choose from.
These different classes are documented later in Chapter \ref{chap:FilterTypeReference}.
For this example we will use the :class:`vtkm::filter::FilterField` base class, which is used to derive one field from another field.
Filters in |VTKm| are implemented by deriving :class:`vtkm::filter::Filter`.
.. todo:: Fix above reference to filter types chapter.
.. 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.
By convention, this declaration would be placed in a header file with a ``.h`` extension.
|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``.
@ -31,6 +29,8 @@ 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``.
@ -40,6 +40,8 @@ For this library, CMake creates a header file named ``vtkm/filter/unit_conversio
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.
@ -62,28 +64,34 @@ The meat of the filter implementation is located in the :func:`vtkm::filter::Fil
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::FilterField` base class provides several methods 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::FilterField::GetFieldFromDataSet` method provided by :class:`vtkm::filter::FilterField` as shown in :exlineref:`ex:SimpleFieldDoExecute:InputField`.
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::FilterField` contains some convenience functions to simplify this.
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::FilterField` provides the :func:`vtkm::filter::FilterField::CastAndCallScalarField` helper method.
The first argument to :func:`vtkm::filter::FilterField::CastAndCallScalarField` is the field containing the data to operate on.
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::FilterField::CastAndCallScalarField` will pull a :class:`vtkm::cont::ArrayHandle` out of the field and call the provided functor with that object.
:func:`vtkm::filter::FilterField::CastAndCallScalarField` is called in :exlineref:`ex:SimpleFieldDoExecute:CastAndCall`.
: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 ``vtkm::Vec`s of a particular size (e.g. 3), you can use the convenience method :func:`vtkm::filter::FilterField::CastAndCallVecField`.
:func:`vtkm::filter::FilterField::CastAndCallVecField` works similarly to :func:`vtkm::filter::FilterField::CastAndCallScalarField` except that it takes a template parameter specifying the size of the ``vtkm::Vec`.
For example, ``vtkm::filter::FilterField::CastAndCallVecField<3>(inField, functor);``.
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::FilterField::CastAndCallScalarField` is a functor that contains the routine to call with the found :class:`vtkm::cont::ArrayHandle`.
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>`.
@ -104,15 +112,24 @@ Recall from :numref:`ex:SimpleFieldConstructor` that by default we set the outpu
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::FilterField::CreateResultField`, which constructs a :class:`vtkm::cont::DataSet` with the same structure as the input and adds the computed filter.
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 classes.
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 Chapter \ref{chap:FilterTypeReference}.
.. todo:: Fix above reference to filter type reference.
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`.

@ -30,6 +30,8 @@ set(examples_device
GuideExampleCellLocator.cxx
GuideExampleDataSetCreation.cxx
GuideExampleErrorHandling.cxx
GuideExampleFilterDataSetWithField.cxx
GuideExampleGenerateMeshConstantShape.cxx
GuideExampleSimpleAlgorithm.cxx
GuideExampleUseWorkletMapField.cxx
GuideExampleUseWorkletPointNeighborhood.cxx

@ -0,0 +1,178 @@
//============================================================================
// Copyright (c) Kitware, Inc.
// All rights reserved.
// See LICENSE.txt for details.
//
// This software is distributed WITHOUT ANY WARRANTY; without even
// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the above copyright notice for more information.
//============================================================================
#include <vtkm/filter/Filter.h>
#include <vtkm/filter/entity_extraction/Threshold.h>
#include <vtkm/TypeList.h>
#include <vtkm/UnaryPredicates.h>
#include <vtkm/cont/ArrayCopyDevice.h>
#include <vtkm/cont/ArrayHandleTransform.h>
#include <vtkm/cont/DataSet.h>
#include <vtkm/cont/testing/MakeTestDataSet.h>
#include <vtkm/cont/testing/Testing.h>
#undef VTKM_FILTER_ENTITY_EXTRACTION_EXPORT
#define VTKM_FILTER_ENTITY_EXTRACTION_EXPORT
////
//// BEGIN-EXAMPLE BlankCellsFilterDeclaration
////
namespace vtkm
{
namespace filter
{
namespace entity_extraction
{
//// PAUSE-EXAMPLE
namespace
{
//// RESUME-EXAMPLE
class VTKM_FILTER_ENTITY_EXTRACTION_EXPORT BlankCells : public vtkm::filter::Filter
{
public:
VTKM_CONT vtkm::cont::DataSet DoExecute(const vtkm::cont::DataSet& inDataSet) override;
};
//// PAUSE-EXAMPLE
} // anonymous namespace
//// RESUME-EXAMPLE
} // namespace entity_extraction
} // namespace filter
} // namespace vtkm
////
//// END-EXAMPLE BlankCellsFilterDeclaration
////
namespace vtkm
{
namespace filter
{
namespace entity_extraction
{
namespace
{
////
//// BEGIN-EXAMPLE BlankCellsFilterDoExecute
////
VTKM_CONT vtkm::cont::DataSet BlankCells::DoExecute(const vtkm::cont::DataSet& inData)
{
vtkm::cont::Field inField = this->GetFieldFromDataSet(inData);
if (!inField.IsCellField())
{
throw vtkm::cont::ErrorBadValue("Blanking field must be a cell field.");
}
// Set up this array to have a 0 for any cell to be removed and
// a 1 for any cell to keep.
vtkm::cont::ArrayHandle<vtkm::FloatDefault> blankingArray;
auto resolveType = [&](const auto& inFieldArray) {
auto transformArray =
vtkm::cont::make_ArrayHandleTransform(inFieldArray, vtkm::NotZeroInitialized{});
vtkm::cont::ArrayCopyDevice(transformArray, blankingArray);
};
this->CastAndCallScalarField(inField, resolveType);
// Make a temporary DataSet (shallow copy of the input) to pass blankingArray
// to threshold.
vtkm::cont::DataSet tempData = inData;
tempData.AddCellField("vtkm-blanking-array", blankingArray);
// Just use the Threshold filter to implement the actual cell removal.
vtkm::filter::entity_extraction::Threshold thresholdFilter;
thresholdFilter.SetLowerThreshold(0.5);
thresholdFilter.SetUpperThreshold(2.0);
thresholdFilter.SetActiveField("vtkm-blanking-array",
vtkm::cont::Field::Association::Cells);
// Make sure threshold filter passes all the fields requested, but not the
// blanking array.
thresholdFilter.SetFieldsToPass(this->GetFieldsToPass());
thresholdFilter.SetFieldsToPass("vtkm-blanking-array",
vtkm::cont::Field::Association::Cells,
vtkm::filter::FieldSelection::Mode::Exclude);
// Use the threshold filter to generate the actual output.
return thresholdFilter.Execute(tempData);
}
////
//// END-EXAMPLE BlankCellsFilterDoExecute
////
} // anonymous namespace
} // namespace entity_extraction
} // namespace filter
} // namespace vtkm
VTKM_CONT
static void DoTest()
{
std::cout << "Setting up data" << std::endl;
vtkm::cont::testing::MakeTestDataSet makedata;
vtkm::cont::DataSet inData = makedata.Make3DExplicitDataSetCowNose();
vtkm::Id numInCells = inData.GetCellSet().GetNumberOfCells();
using FieldType = vtkm::Float32;
vtkm::cont::ArrayHandle<FieldType> inField;
inField.Allocate(numInCells);
SetPortal(inField.WritePortal());
inData.AddCellField("field", inField);
vtkm::cont::ArrayHandle<vtkm::IdComponent> maskArray;
maskArray.Allocate(numInCells);
auto maskPortal = maskArray.WritePortal();
for (vtkm::Id cellIndex = 0; cellIndex < numInCells; ++cellIndex)
{
maskPortal.Set(cellIndex, static_cast<vtkm::IdComponent>(cellIndex % 2));
}
inData.AddCellField("mask", maskArray);
std::cout << "Run filter" << std::endl;
vtkm::filter::entity_extraction::BlankCells filter;
filter.SetActiveField("mask", vtkm::cont::Field::Association::Cells);
// NOTE 2018-03-21: I expect this to fail in the short term. Right now no fields
// are copied from input to output. The default should be changed to copy them
// all. (Also, I'm thinking it would be nice to have a mode to select all except
// a particular field or list of fields.)
vtkm::cont::DataSet outData = filter.Execute(inData);
std::cout << "Checking output." << std::endl;
vtkm::Id numOutCells = numInCells / 2;
VTKM_TEST_ASSERT(outData.GetCellSet().GetNumberOfCells() == numOutCells,
"Unexpected number of cells.");
vtkm::cont::Field outCellField = outData.GetField("field");
vtkm::cont::ArrayHandle<FieldType> outField;
outCellField.GetData().AsArrayHandle(outField);
auto outFieldPortal = outField.ReadPortal();
for (vtkm::Id cellIndex = 0; cellIndex < numOutCells; ++cellIndex)
{
FieldType expectedValue = TestValue(2 * cellIndex + 1, FieldType());
VTKM_TEST_ASSERT(test_equal(outFieldPortal.Get(cellIndex), expectedValue));
}
}
int GuideExampleFilterDataSetWithField(int argc, char* argv[])
{
return vtkm::cont::testing::Testing::Run(DoTest, argc, argv);
}

@ -0,0 +1,291 @@
//============================================================================
// Copyright (c) Kitware, Inc.
// All rights reserved.
// See LICENSE.txt for details.
//
// This software is distributed WITHOUT ANY WARRANTY; without even
// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the above copyright notice for more information.
//============================================================================
#include <vtkm/cont/ArrayCopy.h>
#include <vtkm/cont/ArrayHandleGroupVec.h>
#include <vtkm/cont/ArrayHandlePermutation.h>
#include <vtkm/cont/CellSetSingleType.h>
#include <vtkm/exec/CellEdge.h>
#include <vtkm/worklet/ScatterCounting.h>
#include <vtkm/worklet/WorkletMapTopology.h>
#include <vtkm/filter/Filter.h>
#include <vtkm/filter/MapFieldPermutation.h>
#include <vtkm/cont/testing/MakeTestDataSet.h>
#include <vtkm/cont/testing/Testing.h>
namespace vtkm
{
namespace worklet
{
namespace
{
////
//// BEGIN-EXAMPLE GenerateMeshConstantShapeCount
////
struct CountEdgesWorklet : vtkm::worklet::WorkletVisitCellsWithPoints
{
using ControlSignature = void(CellSetIn cellSet, FieldOut numEdges);
using ExecutionSignature = _2(CellShape, PointCount);
using InputDomain = _1;
template<typename CellShapeTag>
VTKM_EXEC_CONT vtkm::IdComponent operator()(CellShapeTag cellShape,
vtkm::IdComponent numPointsInCell) const
{
vtkm::IdComponent numEdges;
vtkm::ErrorCode status =
vtkm::exec::CellEdgeNumberOfEdges(numPointsInCell, cellShape, numEdges);
if (status != vtkm::ErrorCode::Success)
{
// There is an error in the cell. As good as it would be to return an
// error, we probably don't want to invalidate the entire run if there
// is just one malformed cell. Instead, ignore the cell.
return 0;
}
return numEdges;
}
};
////
//// END-EXAMPLE GenerateMeshConstantShapeCount
////
////
//// BEGIN-EXAMPLE GenerateMeshConstantShapeGenIndices
////
class EdgeIndicesWorklet : public vtkm::worklet::WorkletVisitCellsWithPoints
{
public:
using ControlSignature = void(CellSetIn cellSet, FieldOut connectivityOut);
using ExecutionSignature = void(CellShape, PointIndices, _2, VisitIndex);
using InputDomain = _1;
using ScatterType = vtkm::worklet::ScatterCounting;
template<typename CellShapeTag, typename PointIndexVecType>
VTKM_EXEC void operator()(CellShapeTag cellShape,
const PointIndexVecType& globalPointIndicesForCell,
vtkm::Id2& connectivityOut,
vtkm::IdComponent edgeIndex) const
{
vtkm::IdComponent numPointsInCell =
globalPointIndicesForCell.GetNumberOfComponents();
vtkm::ErrorCode status;
vtkm::IdComponent pointInCellIndex0;
status = vtkm::exec::CellEdgeLocalIndex(
numPointsInCell, 0, edgeIndex, cellShape, pointInCellIndex0);
if (status != vtkm::ErrorCode::Success)
{
this->RaiseError(vtkm::ErrorString(status));
return;
}
vtkm::IdComponent pointInCellIndex1;
status = vtkm::exec::CellEdgeLocalIndex(
numPointsInCell, 1, edgeIndex, cellShape, pointInCellIndex1);
if (status != vtkm::ErrorCode::Success)
{
this->RaiseError(vtkm::ErrorString(status));
return;
}
connectivityOut[0] = globalPointIndicesForCell[pointInCellIndex0];
connectivityOut[1] = globalPointIndicesForCell[pointInCellIndex1];
}
};
////
//// END-EXAMPLE GenerateMeshConstantShapeGenIndices
////
} // anonymous namespace
} // namespace worklet
} // namespace vtkm
////
//// BEGIN-EXAMPLE ExtractEdgesFilterDeclaration
////
namespace vtkm
{
namespace filter
{
namespace entity_extraction
{
//// PAUSE-EXAMPLE
namespace
{
#define VTKM_FILTER_ENTITY_EXTRACTION_EXPORT
//// RESUME-EXAMPLE
class VTKM_FILTER_ENTITY_EXTRACTION_EXPORT ExtractEdges : public vtkm::filter::Filter
{
public:
VTKM_CONT vtkm::cont::DataSet DoExecute(const vtkm::cont::DataSet& inData) override;
};
//// PAUSE-EXAMPLE
} // anonymous namespace
//// RESUME-EXAMPLE
} // namespace entity_extraction
} // namespace filter
} // namespace vtkm
////
//// END-EXAMPLE ExtractEdgesFilterDeclaration
////
namespace vtkm
{
namespace filter
{
namespace entity_extraction
{
//// PAUSE-EXAMPLE
namespace
{
//// RESUME-EXAMPLE
// TODO: It would be nice if there was a simpler example of DoExecute.
////
//// BEGIN-EXAMPLE ExtractEdgesFilterDoExecute
////
////
//// BEGIN-EXAMPLE GenerateMeshConstantShapeInvoke
////
inline VTKM_CONT vtkm::cont::DataSet ExtractEdges::DoExecute(
const vtkm::cont::DataSet& inData)
{
auto inCellSet = inData.GetCellSet();
// Count number of edges in each cell.
vtkm::cont::ArrayHandle<vtkm::IdComponent> edgeCounts;
this->Invoke(vtkm::worklet::CountEdgesWorklet{}, inCellSet, edgeCounts);
// Build the scatter object (for non 1-to-1 mapping of input to output)
vtkm::worklet::ScatterCounting scatter(edgeCounts);
//// LABEL GetOutputToInputMap
auto outputToInputCellMap = scatter.GetOutputToInputMap(inCellSet.GetNumberOfCells());
vtkm::cont::ArrayHandle<vtkm::Id> connectivityArray;
//// LABEL InvokeEdgeIndices
this->Invoke(vtkm::worklet::EdgeIndicesWorklet{},
scatter,
inCellSet,
vtkm::cont::make_ArrayHandleGroupVec<2>(connectivityArray));
vtkm::cont::CellSetSingleType<> outCellSet;
outCellSet.Fill(
inCellSet.GetNumberOfPoints(), vtkm::CELL_SHAPE_LINE, 2, connectivityArray);
// This lambda function maps an input field to the output data set. It is
// used with the CreateResult method.
//// LABEL FieldMapper
auto fieldMapper = [&](vtkm::cont::DataSet& outData,
const vtkm::cont::Field& inputField) {
if (inputField.IsCellField())
{
//// LABEL MapField
vtkm::filter::MapFieldPermutation(inputField, outputToInputCellMap, outData);
}
else
{
outData.AddField(inputField); // pass through
}
};
//// LABEL CreateResult
return this->CreateResult(inData, outCellSet, fieldMapper);
}
////
//// END-EXAMPLE GenerateMeshConstantShapeInvoke
////
////
//// END-EXAMPLE ExtractEdgesFilterDoExecute
////
//// PAUSE-EXAMPLE
} // anonymous namespace
//// RESUME-EXAMPLE
} // namespace entity_extraction
} // namespace filter
} // namespace vtkm
namespace
{
void CheckOutput(const vtkm::cont::CellSetSingleType<>& cellSet)
{
std::cout << "Num cells: " << cellSet.GetNumberOfCells() << std::endl;
VTKM_TEST_ASSERT(cellSet.GetNumberOfCells() == 12 + 8 + 6 + 9, "Wrong # of cells.");
auto connectivity = cellSet.GetConnectivityArray(vtkm::TopologyElementTagCell(),
vtkm::TopologyElementTagPoint());
std::cout << "Connectivity:" << std::endl;
vtkm::cont::printSummary_ArrayHandle(connectivity, std::cout, true);
auto connectivityPortal = connectivity.ReadPortal();
VTKM_TEST_ASSERT(connectivityPortal.Get(0) == 0, "Bad edge index");
VTKM_TEST_ASSERT(connectivityPortal.Get(1) == 1, "Bad edge index");
VTKM_TEST_ASSERT(connectivityPortal.Get(2) == 1, "Bad edge index");
VTKM_TEST_ASSERT(connectivityPortal.Get(3) == 5, "Bad edge index");
VTKM_TEST_ASSERT(connectivityPortal.Get(68) == 9, "Bad edge index");
VTKM_TEST_ASSERT(connectivityPortal.Get(69) == 10, "Bad edge index");
}
void TryFilter()
{
std::cout << std::endl << "Trying calling filter." << std::endl;
vtkm::cont::DataSet inDataSet =
vtkm::cont::testing::MakeTestDataSet().Make3DExplicitDataSet5();
vtkm::filter::entity_extraction::ExtractEdges filter;
vtkm::cont::DataSet outDataSet = filter.Execute(inDataSet);
vtkm::cont::CellSetSingleType<> outCellSet;
outDataSet.GetCellSet().AsCellSet(outCellSet);
CheckOutput(outCellSet);
vtkm::cont::Field outCellField = outDataSet.GetField("cellvar");
VTKM_TEST_ASSERT(outCellField.GetAssociation() ==
vtkm::cont::Field::Association::Cells,
"Cell field not cell field.");
vtkm::cont::ArrayHandle<vtkm::Float32> outCellData;
outCellField.GetData().AsArrayHandle(outCellData);
std::cout << "Cell field:" << std::endl;
vtkm::cont::printSummary_ArrayHandle(outCellData, std::cout, true);
VTKM_TEST_ASSERT(outCellData.GetNumberOfValues() == outCellSet.GetNumberOfCells(),
"Bad size of field.");
auto cellFieldPortal = outCellData.ReadPortal();
VTKM_TEST_ASSERT(test_equal(cellFieldPortal.Get(0), 100.1), "Bad field value.");
VTKM_TEST_ASSERT(test_equal(cellFieldPortal.Get(1), 100.1), "Bad field value.");
VTKM_TEST_ASSERT(test_equal(cellFieldPortal.Get(34), 130.5), "Bad field value.");
}
void DoTest()
{
TryFilter();
}
} // anonymous namespace
int GuideExampleGenerateMeshConstantShape(int argc, char* argv[])
{
return vtkm::cont::testing::Testing::Run(DoTest, argc, argv);
}

@ -0,0 +1,223 @@
===================================
Extended Filter Implementations
===================================
.. index::
double: filter; implementation
In :chapref:`simple-worklets:Simple Worklets` and :chapref:`worklet-types:Worklet Types` we discuss how to implement an algorithm in the |VTKm| framework by creating a worklet.
For simplicity, worklet algorithms are wrapped in what are called filter objects for general usage.
:chapref:`running-filters:Running Filters` introduces the concept of filters, and :chapref:`provided-filters:Provided Filters` documents those that come with the |VTKm| library.
:chapref:`basic-filter-impl:Basic Filter Implementation` gives a brief introduction on implementing filters.
This chapter elaborates on building new filter objects by introducing new filter types.
These will be used to wrap filters around the extended worklet examples in :chapref:`worklet-types:Worklet Types`.
Unsurprisingly, the base filter objects are contained in the ``vtkm::filter`` package.
In particular, all filter objects inherit from :class:`vtkm::filter::Filter`, either directly or indirectly.
The filter implementation must override the protected pure virtual method :func:`vtkm::filter::Filter::DoExecute`.
The base class will call this method to run the operation of the filter.
The :func:`vtkm::filter::Filter::DoExecute` method has a single argument that is a :class:`vtkm::cont::DataSet`.
The :class:`vtkm::cont::DataSet` contains the data on which the filter will operate.
:func:`vtkm::filter::Filter::DoExecute` must then return a new :class:`vtkm::cont::DataSet` containing the derived data.
The :class:`vtkm::cont::DataSet` should be created with one of the :func:`vtkm::filter::Filter::CreateResult` methods.
A filter implementation may also optionally override the :func:`vtkm::filter::Filter::DoExecutePartitions`.
This method is similar to :func:`vtkm::filter::Filter::DoExecute` except that it takes and returns a :class:`vtkm::cont::PartitionedDataSet` object.
If a filter does not provide a :func:`vtkm::filter::Filter::DoExecutePartitions` method, then if given a :class:`vtkm::cont::PartitionedDataSet`, the base class will call :func:`vtkm::filter::Filter::DoExecute` on each of the partitions and build a :class:`vtkm::cont::PartitionedDataSet` with the results.
In addition to (or instead of) operating on the geometric structure of a :class:`vtkm::cont::DataSet`, a filter will commonly take one or more fields from the input :class:`vtkm::cont::DataSet` and write one or more fields to the result.
For this reason, :class:`vtkm::filter::Filter` provides convenience methods to select input fields and output field names.
It also provides a method named :func:`vtkm::filter::Filter::GetFieldFromDataSet` that can be used to get the input fields from the :class:`vtkm::cont::DataSet` passed to :func:`vtkm::filter::Filter::DoExecute`.
When getting a field with :func:`vtkm::filter::Filter::GetFieldFromDataSet`, you get a :class:`vtkm::cont::Field` object.
Before you can operate on the :class:`vtkm::cont::Field`, you have to convert it to a :class:`vtkm::cont::ArrayHandle`.
:func:`vtkm::filter::Filter::CastAndCallScalarField` can be used to do this conversion.
It takes the field object as the first argument and attempts to convert it to an :class:`vtkm::cont::ArrayHandle` of different types.
When it finds the correct type, it calls the provided functor with the appropriate :class:`vtkm::cont::ArrayHandle`.
The similar :func:`vtkm::filter::Filter::CastAndCallVecField` does the same thing to find an :class:`vtkm::cont::ArrayHandle` with :class:`vtkm::Vec`'s of a selected length, and :func:`vtkm::filter::Filter::CastAndCallVariableVecField` does the same thing but will find :class:`vtkm::Vec`'s of any length.
The remainder of this chapter will provide some common patterns of filter operation based on the data they use and generate.
-----------------------------------
Deriving Fields from other Fields
-----------------------------------
.. index::
double: filter; field
A common type of filter is one that generates a new field that is derived from one or more existing fields or point coordinates on the data set.
For example, mass, volume, and density are interrelated, and any one can be derived from the other two.
Typically, you would use :func:`vtkm::filter::Filter::GetFieldFromDataSet` to retrieve the input fields, one of the :func:`vtkm::filter::Filter::CastAndCall` methods to resolve the array type of the field, and finally use :func:`vtkm::filter::Filter::CreateResultField` to produce the output.
In this section we provide an example implementation of a field filter that wraps the "magnitude" worklet provided in :numref:`ex:UseWorkletMapField`.
By C++ convention, object implementations are split into two files.
The first file is a standard header file with a :file:`.h` extension that contains the declaration of the filter class without the implementation.
So we would expect the following code to be in a file named :file:`FieldMagnitude.h`.
.. load-example:: UseFilterField
:file: GuideExampleUseWorkletMapField.cxx
:caption: Header declaration for a field filter.
.. index::
double: export macro; filter
You may notice in :exlineref:`UseFilterField:Export` there is a special macro names :c:macro:`VTKM_FILTER_VECTOR_CALCULUS_EXPORT`.
This macro tells the C++ compiler that the class ``FieldMagnitude`` is going to be exported from a library.
More specifically, the CMake for |VTKm|'s build will generate a header file containing this export macro for the associated library.
By |VTKm|'s convention, a filter in the ``vtkm::filter::vector_calculus`` will be defined in the :file:`vtkm/filter/vector_calculus` directory.
When defining the targets for this library, CMake will create a header file named :file:`vtkm_filter_vector_calculus.h` that contains the macro named :c:macro:`VTKM_FILTER_VECTOR_CALCULUS_EXPORT`.
This macro will provide the correct modifiers for the particular C++ compiler being used to export the class from the library.
If this macro is left out, then the library will work on some platforms, but on other platforms will produce a linker error for missing symbols.
Once the filter class is declared in the :file:`.h` file, the implementation filter is by convention given in a separate :file:`.cxx` file.
So the continuation of our example that follows would be expected in a file named :file:`FieldMagnitude.cxx`.
.. load-example:: FilterFieldImpl
:file: GuideExampleUseWorkletMapField.cxx
:caption: Implementation of a field filter.
The implementation of :func:`vtkm::filter::Filter::DoExecute` first pulls the input field from the provided :class:`vtkm::cont::DataSet` using :func:`vtkm::filter::Filter::GetFieldFromDataSet`.
It then uses :func:`vtkm::filter::Filter::CastAndCallVecField` to determine what type of :class:`vtkm::cont::ArrayHandle` is contained in the input field.
That calls a lambda function that invokes a worklet to create the output field.
.. doxygenfunction:: vtkm::filter::Filter::CastAndCallVecField(const vtkm::cont::UnknownArrayHandle&, Functor&&, Args&&...) const
.. doxygenfunction:: vtkm::filter::Filter::CastAndCallVecField(const vtkm::cont::Field&, Functor&&, Args&&...) const
.. didyouknow::
The filter implemented in :numref:`ex:FilterFieldImpl` is limited to only find the magnitude of :class:`vtkm::Vec`'s with 3 components.
It may be the case you wish to implement a filter that operates on :class:`vtkm::Vec`'s of multiple sizes (or perhaps even any size).
Chapter \ref{chap:UnknownArrayHandle} discusses how you can use the :class:`vtkm::cont::UnknownArrayHandle` contained in the :class:`vtkm::cont::Field` to more expressively decide what types to check for.
.. doxygenfunction:: vtkm::filter::Filter::CastAndCallVariableVecField(const vtkm::cont::UnknownArrayHandle&, Functor&&, Args&&...) const
.. doxygenfunction:: vtkm::filter::Filter::CastAndCallVariableVecField(const vtkm::cont::Field&, Functor&&, Args&&...) const
.. todo:: Fix reference to unknown array handle above.
Finally, :func:`vtkm::filter::Filter::CreateResultField` generates the output of the filter.
Note that all fields need a unique name, which is the reason for the second argument to :func:`vtkm::filter::Filter::CreateResult`.
The :class:`vtkm::filter::Filter` base class contains a pair of methods named :func:`vtkm::filter::Filter::SetOutputFieldName` and :func:`vtkm::filter::Filter::GetOutputFieldName` to allow users to specify the name of output fields.
The :func:`vtkm::filter::Filter::DoExecute` method should respect the given output field name.
However, it is also good practice for the filter to have a default name if none is given.
This might be simply specifying a name in the constructor, but it is worthwhile for many filters to derive a name based on the name of the input field.
------------------------------
Deriving Fields from Topology
------------------------------
.. index::
double: filter; using cells
The previous example performed a simple operation on each element of a field independently.
However, it is also common for a "field" filter to take into account the topology of a data set.
In this case, the implementation involves pulling a :class:`vtkm::cont::CellSet` from the input :class:`vtkm::cont::DataSet` and performing operations on fields associated with different topological elements.
The steps involve calling :func:`vtkm::cont::DataSet::GetCellSet` to get access to the :class:`vtkm::cont::CellSet` object and then using topology-based worklets, described in :secref:`worklet-types:Topology Map`, to operate on them.
In this section we provide an example implementation of a field filter on cells that wraps the "cell center" worklet provided in :numref:`ex:UseWorkletVisitCellsWithPoints`.
.. load-example:: UseFilterFieldWithCells
:file: GuideExampleUseWorkletVisitCellsWithPoints.cxx
:caption: Header declaration for a field filter using cell topology.
As with any subclass of :class:`vtkm::filter::Filter`, the filter implements :func:`vtkm::filter::Filter::DoExecute`, which in this case invokes a worklet to compute a new field array and then return a newly constructed :class:`vtkm::cont::DataSet` object.
.. load-example:: FilterFieldWithCellsImpl
:file: GuideExampleUseWorkletVisitCellsWithPoints.cxx
:caption: Implementation of a field filter using cell topology.
.. todo:: The CastAndCall is too complex here. Probably should add a CastAndCallScalarOrVec to FilterField.
------------------------------
Data Set Filters
------------------------------
.. index::
double: filter; data set
Sometimes, a filter will generate a data set with a new cell set based off the cells of an input data set.
For example, a data set can be significantly altered by adding, removing, or replacing cells.
As with any filter, data set filters can be implemented in classes that derive the :class:`vtkm::filter::Filter` base class and implement its :func:`vtkm::filter::Filter::DoExecute` method.
In this section we provide an example implementation of a data set filter that wraps the functionality of extracting the edges from a data set as line elements.
Many variations of implementing this functionality are given in Chapter~\ref{chap:GeneratingCellSets}.
Suffice it to say that a pair of worklets will be used to create a new :class:`vtkm::cont::CellSet`, and this :class:`vtkm::cont::CellSet` will be used to create the result :class:`vtkm::cont::DataSet`.
Details on how the worklets work are given in Section \ref{sec:GeneratingCellSets:SingleType}.
.. todo:: Fix reference to generating cell sets.
Because the operation of this edge extraction depends only on :class:`vtkm::cont::CellSet` in a provided :class:`vtkm::cont::DataSet`, the filter class is a simple subclass of :class:`vtkm::filter::Filter`.
.. load-example:: ExtractEdgesFilterDeclaration
:file: GuideExampleGenerateMeshConstantShape.cxx
:caption: Header declaration for a data set filter.
The implementation of :func:`vtkm::filter::Filter::DoExecute` first gets the :class:`vtkm::cont::CellSet` and calls the worklet methods to generate a new :class:`vtkm::cont::CellSet` class.
It then uses a form of :func:`vtkm::filter::Filter::CreateResult` to generate the resulting :class:`vtkm::cont::DataSet`.
.. load-example:: ExtractEdgesFilterDoExecute
:file: GuideExampleGenerateMeshConstantShape.cxx
:caption: Implementation of the :func:`vtkm::filter::Filter::DoExecute` method of a data set filter.
The form of :func:`vtkm::filter::Filter::CreateResult` used (:exlineref:`ex:ExtractEdgesFilterDoExecute:CreateResult`) takes as input a :class:`vtkm::cont::CellSet` to use in the generated data.
In forms of :func:`vtkm::filter::Filter::CreateResult` used in previous examples of this chapter, the cell structure of the output was created from the cell structure of the input.
Because these cell structures were the same, coordinate systems and fields needed to be changed.
However, because we are providing a new :class:`vtkm::cont::CellSet`, we need to also specify how the coordinate systems and fields change.
The last two arguments to :func:`vtkm::filter::Filter::CreateResult` are providing this information.
The second-to-last argument is a ``std::vector`` of the :class:`vtkm::cont::CoordinateSystem`'s to use.
Because this filter does not actually change the points in the data set, the :class:`vtkm::cont::CoordinateSystem`'s can just be copied over.
The last argument provides a functor that maps a field from the input to the output.
The functor takes two arguments: the output :class:`vtkm::cont::DataSet` to modify and the input :class:`vtkm::cont::Field` to map.
In this example, the functor is defined as a lambda function (:exlineref:`ex:ExtractEdgesFilterDoExecute:FieldMapper`).
.. didyouknow::
The field mapper in :numref:`ex:ExtractEdgesFilterDeclaration` uses a helper function named :func:`vtkm::filter::MapFieldPermutation`.
In the case of this example, every cell in the output comes from one cell in the input.
For this common case, the values in the field arrays just need to be permuted so that each input value gets to the right output value.
:func:`vtkm::filter::MapFieldPermutation` will do this shuffling for you.
|VTKm| also comes with a similar helper function :func:`vtkm::filter::MapFieldMergeAverage` that can be used when each output cell (or point) was constructed from multiple inputs.
In this case, :func:`vtkm::filter::MapFieldMergeAverage` can do a simple average for each output value of all input values that contributed.
.. doxygenfunction:: vtkm::filter::MapFieldPermutation(const vtkm::cont::Field&, const vtkm::cont::ArrayHandle<vtkm::Id>&, vtkm::cont::Field&, vtkm::Float64)
.. doxygenfunction:: vtkm::filter::MapFieldPermutation(const vtkm::cont::Field&, const vtkm::cont::ArrayHandle<vtkm::Id>&, vtkm::cont::DataSet&, vtkm::Float64)
.. doxygenfunction:: vtkm::filter::MapFieldMergeAverage(const vtkm::cont::Field&, const vtkm::worklet::internal::KeysBase&, vtkm::cont::Field&)
.. doxygenfunction:: vtkm::filter::MapFieldMergeAverage(const vtkm::cont::Field&, const vtkm::worklet::internal::KeysBase&, vtkm::cont::DataSet&)
.. didyouknow::
Although not the case in this example, sometimes a filter creating a new cell set changes the points of the cells.
As long as the field mapper you provide to :func:`vtkm::filter::Filter::CreateResult` properly converts points from the input to the output, all fields and coordinate systems will be automatically filled in the output.
Sometimes when creating this new cell set you also create new point coordinates for it.
This might be because the point coordinates are necessary for the computation or might be due to a faster way of computing the point coordinates.
In either case, if the filter already has point coordinates computed, it can use :func:`vtkm::filter::Filter::CreateResultCoordinateSystem` to use the precomputed point coordinates.
------------------------------
Data Set with Field Filters
------------------------------
.. index::
double: filter; data set with field
Sometimes, a filter will generate a data set with a new cell set based off the cells of an input data set along with the data in at least one field.
For example, a field might determine how each cell is culled, clipped, or sliced.
In this section we provide an example implementation of a data set with field filter that blanks the cells in a data set based on a field that acts as a mask (or stencil).
Any cell associated with a mask value of zero will be removed.
For simplicity of this example, we will use the :class:`vtkm::filter::entity_extraction::Threshold` filter internally for the implementation.
.. load-example:: BlankCellsFilterDeclaration
:file: GuideExampleFilterDataSetWithField.cxx
:caption: Header declaration for a data set with field filter.
The implementation of :func:`vtkm::filter::Filter::DoExecute` first derives an array that contains a flag whether the input array value is zero or non-zero.
This is simply to guarantee the range for the threshold filter.
After that a threshold filter is set up and run to generate the result.
.. load-example:: BlankCellsFilterDoExecute
:file: GuideExampleFilterDataSetWithField.cxx
:caption: Implementation of the :func:`vtkm::filter::Filter::DoExecute` method of a data set with field filter.

@ -8,5 +8,6 @@ Advanced Development
advanced-types.rst
logging.rst
worklet-types.rst
extended-filter-impl.rst
worklet-error-handling.rst
math.rst

@ -221,7 +221,7 @@ See :chapref:`implicit-functions:Implicit Functions` for information on implicit
.. doxygenclass:: vtkm::filter::contour::Slice
:members:
The :class:`vtkm::filter::contour::Slice` filter inherits from the :class:`vtkm::filter::contour::Contour`, uses its implementation to extract the slices, and several of the inherited methods are useful including :func:`vtkm::filter::contour::AbstractContour::SetGenerateNormals`, :func:`vtkm::filter::contour::AbstractContour::GetGenerateNormals`, :func:`vtkm::filter::contour::AbstractContour::SetComputeFastNormals`, :func:`vtkm::filter::contour::AbstractContour::GetComputeFastNormals`, :func:`vtkm::filter::contour::AbstractContour::SetNormalArrayName`, :func:`vtkm::filter::contour::AbstractContour::GetNormalArrayName`, :func:`vtkm::filter::contour::AbstractContour::SetMergeDuplicatePoints`, :func:`vtkm::filter::contour::AbstractContour::GetMergeDuplicatePoints`, :func:`vtkm::filter::FilterField::SetActiveCoordinateSystem`, and :func:`vtkm::filter::FilterField::GetActiveCoordinateSystemIndex`.
The :class:`vtkm::filter::contour::Slice` filter inherits from the :class:`vtkm::filter::contour::Contour`, uses its implementation to extract the slices, and several of the inherited methods are useful including :func:`vtkm::filter::contour::AbstractContour::SetGenerateNormals`, :func:`vtkm::filter::contour::AbstractContour::GetGenerateNormals`, :func:`vtkm::filter::contour::AbstractContour::SetComputeFastNormals`, :func:`vtkm::filter::contour::AbstractContour::GetComputeFastNormals`, :func:`vtkm::filter::contour::AbstractContour::SetNormalArrayName`, :func:`vtkm::filter::contour::AbstractContour::GetNormalArrayName`, :func:`vtkm::filter::contour::AbstractContour::SetMergeDuplicatePoints`, :func:`vtkm::filter::contour::AbstractContour::GetMergeDuplicatePoints`, :func:`vtkm::filter::Field::SetActiveCoordinateSystem`, and :func:`vtkm::filter::Field::GetActiveCoordinateSystemIndex`.
Clip with Field
------------------------------

@ -24,9 +24,14 @@ Basic Filter Operation
Different filters will be used in different ways, but the basic operation of all filters is to instantiate the filter class, set the state parameters on the filter object, and then call the filter's :func:`vtkm::filter::Filter::Execute` method.
It takes a :class:`vtkm::cont::DataSet` and returns a new :class:`vtkm::cont::DataSet`, which contains the modified data.
.. doxygenfunction:: vtkm::filter::Filter::Execute(const vtkm::cont::DataSet&)
The :func:`vtkm::filter::Filter::Execute` method can alternately take a :class:`vtkm::cont::PartitionedDataSet` object, which is a composite of :class:`vtkm::cont::DataSet` objects.
In this case :func:`vtkm::filter::Filter::Execute` will return another :class:`vtkm::cont::PartitionedDataSet` object.
.. doxygenfunction:: vtkm::filter::Filter::Execute(const vtkm::cont::PartitionedDataSet&)
The following example provides a simple demonstration of using a filter.
It specifically uses the point elevation filter to estimate the air pressure at each point based on its elevation.
@ -131,6 +136,9 @@ You can control which fields are passed (and equivalently which are not) with th
.. doxygenfunction:: vtkm::filter::Filter::SetFieldsToPass(vtkm::filter::FieldSelection&&)
.. doxygenfunction:: vtkm::filter::Filter::GetFieldsToPass() const
.. doxygenfunction:: vtkm::filter::Filter::GetFieldsToPass()
There are multiple ways to to use :func:`vtkm::filter::Filter::SetFieldsToPass` to control what fields are passed.
If you want to turn off all fields so that none are passed, call :func:`vtkm::filter::Filter::SetFieldsToPass` with :enum:`vtkm::filter::FieldSelection::Mode::None`.
@ -140,12 +148,17 @@ If you want to turn off all fields so that none are passed, call :func:`vtkm::fi
If you want to pass one specific field, you can pass that field's name to :func:`vtkm::filter::Filter::SetFieldsToPass`.
.. doxygenfunction:: vtkm::filter::Filter::SetFieldsToPass(const std::string&, vtkm::filter::FieldSelection::Mode)
.. doxygenfunction:: vtkm::filter::Filter::SetFieldsToPass(const std::string&, vtkm::cont::Field::Association, vtkm::filter::FieldSelection::Mode)
.. load-example:: PassOneField
:file: GuideExampleProvidedFilters.cxx
:caption: Setting one field to pass by name.
Or you can provide a list of fields to pass by giving :func:`vtkm::filter::Filter::SetFieldsToPass` an initializer list of names.
.. doxygenfunction:: vtkm::filter::Filter::SetFieldsToPass(std::initializer_list<std::string>, vtkm::filter::FieldSelection::Mode)
.. load-example:: PassListOfFields
:file: GuideExampleProvidedFilters.cxx
:caption: Using a list of fields for a filter to pass.
@ -159,6 +172,9 @@ If you want to instead select a list of fields to *not* pass, you can add :enum:
Ultimately, :func:`vtkm::filter::Filter::SetFieldsToPass` takes a :class:`vtkm::filter::FieldSelection` object.
You can create one directly to select (or exclude) specific fields and their associations.
.. doxygenclass:: vtkm::filter::FieldSelection
:members:
.. load-example:: FieldSelection
:file: GuideExampleProvidedFilters.cxx
:caption: Using :class:`vtkm::filter::FieldSelection` to select cells to pass.
@ -168,6 +184,8 @@ If you only have one field, you can just specify both the name and attribution.
If you have multiple fields, you can provide an initializer list of ``std::pair`` or :class:`vtkm::Pair` containing a ``std::string`` and a :enum:`vtkm::cont::Field::Association`.
In either case, you can add an optional last argument of :enum:`vtkm::filter::FieldSelection::Mode::Exclude` to exclude the specified filters instead of selecting them.
.. doxygenfunction:: vtkm::filter::Filter::SetFieldsToPass(std::initializer_list<std::pair<std::string, vtkm::cont::Field::Association>>, vtkm::filter::FieldSelection::Mode)
.. load-example:: PassFieldAndAssociation
:file: GuideExampleProvidedFilters.cxx
:caption: Selecting one field and its association for a filter to pass.
@ -186,3 +204,16 @@ To prevent a filter from passing a coordinate system if its associated field is
.. load-example:: PassNoCoordinates
:file: GuideExampleProvidedFilters.cxx
:caption: Turning off the automatic selection of fields associated with a :class:`vtkm::cont::DataSet`'s coordinate system.
Output Field Names
==============================
Many filters will create fields of data.
A common way to set the name of the output field is to use the :func:`vtkm::filter::Filter::SetOutputFieldName` method.
.. doxygenfunction:: vtkm::filter::Filter::SetOutputFieldName
.. doxygenfunction:: vtkm::filter::Filter::GetOutputFieldName
Most filters will have a default name to use for its generated fields.
It is also common for filters to provide convenience methods to name the output fields.

@ -25,12 +25,12 @@ namespace vtkm
{
namespace filter
{
/// @brief base class for all filters.
/// @brief Base class for all filters.
///
/// This is the base class for all filters. To add a new filter, one can subclass this and
/// implement relevant methods.
///
/// @section FilterUsage Usage
/// **FilterUsage Usage**
///
/// To execute a filter, one typically calls the `auto result = filter.Execute(input)`. Typical
/// usage is as follows:
@ -73,7 +73,7 @@ namespace filter
/// `DoExecutePartitions(PartitionedDataSet&)`. See the implementation of
/// `FilterParticleAdvection::Execute(PartitionedDataSet&)` for an example.
///
/// @section Creating results and mapping fields
/// **Creating results and mapping fields**
///
/// For subclasses that map input fields into output fields, the implementation of its
/// `DoExecute(DataSet&)` should create the `DataSet` to be returned with a call to
@ -106,7 +106,7 @@ namespace filter
/// provided as a convenience that uses the default mapper which trivially adds input Field to
/// output DataSet (via a shallow copy).
///
/// @section FilterThreadSafety CanThread
/// **FilterThreadSafety CanThread**
///
/// By default, the implementation of `DoExecute(DataSet&)` should model a *pure function*, i.e. it
/// does not have any mutable shared state. This makes it thread-safe by default and allows
@ -150,7 +150,8 @@ namespace filter
/// needs to override the `CanThread()` virtual method to return `false`. The default
/// `Execute(PartitionedDataSet&)` implementation will fallback to a serial for loop execution.
///
/// @subsection FilterThreadScheduling DoExecute
/// _FilterThreadScheduling DoExecute_
///
/// The default multi-threaded execution of `Execute(PartitionedDataSet&)` uses a simple FIFO queue
/// of DataSet and pool of *worker* threads. Implementation of Filter subclass can override the
/// `DoExecutePartitions(PartitionedDataSet)` virtual method to provide implementation specific

@ -30,8 +30,9 @@ namespace filter
/// will be be a value from `inputField`, but comes from the index that comes from `permutation` at
/// position _i_. The result is placed in `outputField`.
///
/// The intention of this method is to implement the `MapFieldOntoOutput` methods in filters (many
/// of which require this permutation of a field), but can be used in other places as well.
/// The intention of this method is to implement the mapping of fields from the input to the output
/// in filters (many of which require this permutation of a field), but can be used in other places
/// as well.
///
/// `outputField` is set to have the same metadata as the input. If the metadata needs to change
/// (such as the name or the association) that should be done after the function returns.