diff --git a/docs/changelog/composite-vector-filter.md b/docs/changelog/composite-vector-filter.md index 5c35d3fe3..bd2157426 100644 --- a/docs/changelog/composite-vector-filter.md +++ b/docs/changelog/composite-vector-filter.md @@ -1,3 +1,3 @@ # New Composite Vector filter -The composite vector filter combines multiple scalar fields into a single vector field. Scalar fields are selected as the active input fields, and the combined vector field is set at the output. The current composite vector filter only supports 2d and 3d scalar field composition. Users may use `vtkm::cont::make_ArrayHandleCompositeVector` to execute a more flexible scalar field composition. +The composite vector filter combines multiple scalar fields into a single vector field. Scalar fields are selected as the active input fields, and the combined vector field is set at the output. diff --git a/vtkm/filter/field_transform/CompositeVectors.cxx b/vtkm/filter/field_transform/CompositeVectors.cxx index 526dac91b..d35c1f238 100644 --- a/vtkm/filter/field_transform/CompositeVectors.cxx +++ b/vtkm/filter/field_transform/CompositeVectors.cxx @@ -7,94 +7,119 @@ // the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // PURPOSE. See the above copyright notice for more information. //============================================================================ -#include -#include -#include + #include +#include +#include +#include +#include + +#include +#include + +namespace +{ + +// Extracts a component from an UnknownArrayHandle and returns the extracted component +// as an UnknownArrayHandle. Perhaps this functionality should be part of UnknownArrayHandle +// proper, but its use is probably rare. Note that this implementation makes some assumptions +// on its use in the CompositeVectors filter. +VTKM_CONT vtkm::cont::UnknownArrayHandle ExtractComponent( + const vtkm::cont::UnknownArrayHandle& array, + vtkm::IdComponent componentIndex) +{ + vtkm::cont::UnknownArrayHandle extractedComponentArray; + auto resolveType = [&](auto componentExample) { + using ComponentType = decltype(componentExample); + if (array.IsBaseComponentType()) + { + extractedComponentArray = + array.ExtractComponent(componentIndex, vtkm::CopyFlag::Off); + } + }; + vtkm::ListForEach(resolveType, vtkm::TypeListBaseC{}); + return extractedComponentArray; +} + +} // anonymous namespace + namespace vtkm { namespace filter { namespace field_transform { + +VTKM_CONT void CompositeVectors::SetFieldNameList(const std::vector& fieldNameList, + vtkm::cont::Field::Association association) +{ + vtkm::IdComponent index = 0; + for (auto& fieldName : fieldNameList) + { + this->SetActiveField(index, fieldName, association); + ++index; + } +} + +VTKM_CONT vtkm::IdComponent CompositeVectors::GetNumberOfFields() const +{ + return this->GetNumberOfActiveFields(); +} + VTKM_CONT vtkm::cont::DataSet CompositeVectors::DoExecute(const vtkm::cont::DataSet& inDataSet) { + vtkm::IdComponent numComponents = this->GetNumberOfFields(); + if (numComponents < 1) + { + throw vtkm::cont::ErrorBadValue( + "No input fields to combine into a vector for CompositeVectors."); + } + vtkm::cont::UnknownArrayHandle outArray; - if (this->NumberOfFields < 2) - { - throw vtkm::cont::ErrorFilterExecution("FieldNameList is supposed to be larger than 2."); - } - else if (this->NumberOfFields == 2) - { - auto fieldAssociation = this->GetFieldFromDataSet(0, inDataSet).GetAssociation(); - if (fieldAssociation != this->GetFieldFromDataSet(1, inDataSet).GetAssociation()) + + // Allocate output array to the correct type. + vtkm::cont::Field firstField = this->GetFieldFromDataSet(0, inDataSet); + vtkm::Id numValues = firstField.GetNumberOfValues(); + vtkm::cont::Field::Association association = firstField.GetAssociation(); + auto allocateOutput = [&](auto exampleComponent) { + using ComponentType = decltype(exampleComponent); + if (firstField.GetData().IsBaseComponentType()) { - throw vtkm::cont::ErrorFilterExecution("Field 0 and Field 2 have different associations."); + outArray = vtkm::cont::ArrayHandleRuntimeVec{ numComponents }; } - auto resolveType2d = [&](const auto& field0) { - using T = typename std::decay_t::ValueType; - vtkm::cont::ArrayHandle field1; - vtkm::cont::ArrayCopyShallowIfPossible(this->GetFieldFromDataSet(1, inDataSet).GetData(), - field1); - - auto compositedArray = vtkm::cont::make_ArrayHandleCompositeVector(field0, field1); - - using VecType = vtkm::Vec; - using ArrayHandleType = vtkm::cont::ArrayHandle; - ArrayHandleType result; - vtkm::cont::ArrayCopy(compositedArray, result); - outArray = result; - }; - const auto& inField0 = this->GetFieldFromDataSet(0, inDataSet); - inField0.GetData().CastAndCallForTypes( - resolveType2d); - } - else if (this->NumberOfFields == 3) + }; + vtkm::ListForEach(allocateOutput, vtkm::TypeListBaseC{}); + if (!outArray.IsValid() || (outArray.GetNumberOfComponentsFlat() != numComponents)) { - auto fieldAssociation0 = this->GetFieldFromDataSet(0, inDataSet).GetAssociation(); - auto fieldAssociation1 = this->GetFieldFromDataSet(1, inDataSet).GetAssociation(); - auto fieldAssociation2 = this->GetFieldFromDataSet(2, inDataSet).GetAssociation(); + throw vtkm::cont::ErrorBadType("Unable to allocate output array due to unexpected type."); + } + outArray.Allocate(numValues); - if (fieldAssociation0 != fieldAssociation1 || fieldAssociation1 != fieldAssociation2 || - fieldAssociation0 != fieldAssociation2) + // Iterate over all component fields and copy them into the output array. + for (vtkm::IdComponent componentIndex = 0; componentIndex < numComponents; ++componentIndex) + { + vtkm::cont::Field inScalarField = this->GetFieldFromDataSet(componentIndex, inDataSet); + if (inScalarField.GetData().GetNumberOfComponentsFlat() != 1) { - throw vtkm::cont::ErrorFilterExecution( - "Field 0, Field 1 and Field 2 have different associations."); + throw vtkm::cont::ErrorBadValue("All input fields to CompositeVectors must be scalars."); + } + if (inScalarField.GetAssociation() != association) + { + throw vtkm::cont::ErrorBadValue( + "All scalar fields must have the same association (point, cell, etc.)."); + } + if (inScalarField.GetNumberOfValues() != numValues) + { + throw vtkm::cont::ErrorBadValue("Inconsistent number of field values."); } - auto resolveType3d = [&](const auto& field0) { - using T = typename std::decay_t::ValueType; - vtkm::cont::ArrayHandle field1; - vtkm::cont::ArrayCopyShallowIfPossible(this->GetFieldFromDataSet(1, inDataSet).GetData(), - field1); - vtkm::cont::ArrayHandle field2; - vtkm::cont::ArrayCopyShallowIfPossible(this->GetFieldFromDataSet(2, inDataSet).GetData(), - field2); - auto compositedArray = vtkm::cont::make_ArrayHandleCompositeVector(field0, field1, field2); - - using VecType = vtkm::Vec; - using ArrayHandleType = vtkm::cont::ArrayHandle; - ArrayHandleType result; - // ArrayHandleCompositeVector currently does not implement the ability to - // get to values on the control side, so copy to an array that is accessible. - vtkm::cont::ArrayCopy(compositedArray, result); - outArray = result; - }; - - const auto& inField0 = this->GetFieldFromDataSet(0, inDataSet); - inField0.GetData().CastAndCallForTypes( - resolveType3d); - } - else - { - throw vtkm::cont::ErrorFilterExecution( - "Using make_ArrayHandleCompositeVector to composite vectors more than 3."); + ExtractComponent(outArray, componentIndex).DeepCopyFrom(inScalarField.GetData()); } - return this->CreateResultField( - inDataSet, this->GetOutputFieldName(), this->GetActiveFieldAssociation(), outArray); + return this->CreateResultField(inDataSet, this->GetOutputFieldName(), association, outArray); } + } // namespace field_transform } // namespace vtkm::filter } // namespace vtkm diff --git a/vtkm/filter/field_transform/CompositeVectors.h b/vtkm/filter/field_transform/CompositeVectors.h index 7709e8efb..5cc881f06 100644 --- a/vtkm/filter/field_transform/CompositeVectors.h +++ b/vtkm/filter/field_transform/CompositeVectors.h @@ -20,8 +20,15 @@ namespace filter namespace field_transform { -/// \brief The composite vector filter combines multiple scalar fields into a single vector field. -/// Scalar fields are selected as the active input fields, and the combined vector field is set at the output. +/// \brief Combine multiple scalar fields into a single vector field. +/// +/// Scalar fields are selected as the active input fields, and the combined vector +/// field is set at the output. The `SetFieldNameList` method takes a `std::vector` +/// of field names to use as the component fields. Alternately, the superclass' +/// set active field methods can be used to select the fields independently. +/// +/// All of the input fields must be scalar values. The type of the first field +/// determines the type of the output vector field. /// class VTKM_FILTER_FIELD_TRANSFORM_EXPORT CompositeVectors : public vtkm::filter::FilterField { @@ -33,24 +40,12 @@ public: VTKM_CONT void SetFieldNameList( const std::vector& fieldNameList, - vtkm::cont::Field::Association association = vtkm::cont::Field::Association::Any) - { + vtkm::cont::Field::Association association = vtkm::cont::Field::Association::Any); - vtkm::IdComponent index = 0; - for (auto& fieldName : fieldNameList) - { - this->SetActiveField(index, fieldName, association); - ++index; - } - this->NumberOfFields = static_cast(fieldNameList.size()); - } - - VTKM_CONT - vtkm::IdComponent GetNumberOfFields() { return this->NumberOfFields; } + VTKM_CONT vtkm::IdComponent GetNumberOfFields() const; private: vtkm::cont::DataSet DoExecute(const vtkm::cont::DataSet& input) override; - vtkm::IdComponent NumberOfFields; }; } // namespace field_transform } // namespace vtkm::filter diff --git a/vtkm/filter/field_transform/testing/UnitTestCompositeVectors.cxx b/vtkm/filter/field_transform/testing/UnitTestCompositeVectors.cxx index 2082cb946..4950f82b9 100644 --- a/vtkm/filter/field_transform/testing/UnitTestCompositeVectors.cxx +++ b/vtkm/filter/field_transform/testing/UnitTestCompositeVectors.cxx @@ -20,19 +20,19 @@ vtkm::cont::DataSet MakeDataSet(vtkm::IdComponent numArrays) vtkm::IdComponent arrayLen = 100; - for (vtkm::IdComponent i = 0; i < numArrays; ++i) + for (vtkm::IdComponent fieldIndex = 0; fieldIndex < numArrays; ++fieldIndex) { std::vector pointarray; std::vector cellarray; - for (vtkm::IdComponent j = 0; j < arrayLen; ++j) + for (vtkm::Id valueIndex = 0; valueIndex < arrayLen; ++valueIndex) { - pointarray.push_back(static_cast(i * 1.1 + j * 1.1)); - cellarray.push_back(static_cast(i * 2.1 + j * 2.1)); + pointarray.push_back(static_cast(fieldIndex * 1.1 + valueIndex * 1.1)); + cellarray.push_back(static_cast(fieldIndex * 2.1 + valueIndex * 2.1)); } - dataSet.AddPointField("pointArray" + std::to_string(i), pointarray); - dataSet.AddCellField("cellArray" + std::to_string(i), cellarray); + dataSet.AddPointField("pointArray" + std::to_string(fieldIndex), pointarray); + dataSet.AddCellField("cellArray" + std::to_string(fieldIndex), cellarray); } return dataSet; @@ -44,7 +44,7 @@ void CheckResults(vtkm::cont::DataSet inDataSet, const std::string compositedName) { //there are only three fields for this testing , it is ok to use vtkm::IdComponent - vtkm::IdComponent dims = static_cast(FieldNames.size()); + vtkm::IdComponent numComponents = static_cast(FieldNames.size()); auto compositedField = inDataSet.GetField(compositedName); vtkm::IdComponent compositedFieldLen = static_cast(compositedField.GetNumberOfValues()); @@ -55,9 +55,9 @@ void CheckResults(vtkm::cont::DataSet inDataSet, auto compFieldReadPortal = compFieldArrayCopy.ReadPortal(); - for (vtkm::IdComponent i = 0; i < dims; i++) + for (vtkm::IdComponent componentIndex = 0; componentIndex < numComponents; componentIndex++) { - auto field = inDataSet.GetField(FieldNames[i]); + auto field = inDataSet.GetField(FieldNames[componentIndex]); VTKM_TEST_ASSERT(compositedField.GetAssociation() == field.GetAssociation(), "Got bad association value."); @@ -68,11 +68,11 @@ void CheckResults(vtkm::cont::DataSet inDataSet, field.GetData().AsArrayHandle(fieldArrayHandle); auto fieldReadPortal = fieldArrayHandle.ReadPortal(); - for (vtkm::IdComponent j = 0; j < fieldLen; j++) + for (vtkm::Id valueIndex = 0; valueIndex < fieldLen; valueIndex++) { - auto compFieldVec = compFieldReadPortal.Get(j); - auto comFieldValue = compFieldVec[static_cast(i)]; - auto fieldValue = fieldReadPortal.Get(j); + auto compFieldVec = compFieldReadPortal.Get(valueIndex); + auto comFieldValue = compFieldVec[static_cast(componentIndex)]; + auto fieldValue = fieldReadPortal.Get(valueIndex); VTKM_TEST_ASSERT(comFieldValue == fieldValue, "Got bad field value."); } } @@ -80,27 +80,30 @@ void CheckResults(vtkm::cont::DataSet inDataSet, template -void TestCompositeVectors(vtkm::IdComponent dim) +void TestCompositeVectors(vtkm::IdComponent numComponents) { - vtkm::cont::DataSet inDataSet = MakeDataSet(dim); + vtkm::cont::DataSet inDataSet = MakeDataSet(numComponents); vtkm::filter::field_transform::CompositeVectors filter; + // For the first pass (point field), we are going to use the generic `SetActiveField` method + // and let the filter figure out how many fields. std::vector pointFieldNames; - for (vtkm::IdComponent i = 0; i < dim; i++) + for (vtkm::IdComponent componentIndex = 0; componentIndex < numComponents; componentIndex++) { - pointFieldNames.push_back("pointArray" + std::to_string(i)); + pointFieldNames.push_back("pointArray" + std::to_string(componentIndex)); + filter.SetActiveField(componentIndex, "pointArray" + std::to_string(componentIndex)); } - filter.SetFieldNameList(pointFieldNames, vtkm::cont::Field::Association::Points); filter.SetOutputFieldName("CompositedFieldPoint"); vtkm::cont::DataSet outDataSetPointAssoci = filter.Execute(inDataSet); CheckResults( outDataSetPointAssoci, pointFieldNames, filter.GetOutputFieldName()); + // For the second pass (cell field), we will use the `SetFieldNameList` method. std::vector cellFieldNames; - for (vtkm::IdComponent i = 0; i < dim; i++) + for (vtkm::IdComponent componentIndex = 0; componentIndex < numComponents; componentIndex++) { - cellFieldNames.push_back("cellArray" + std::to_string(i)); + cellFieldNames.push_back("cellArray" + std::to_string(componentIndex)); } filter.SetFieldNameList(cellFieldNames, vtkm::cont::Field::Association::Cells); filter.SetOutputFieldName("CompositedFieldCell"); @@ -114,8 +117,10 @@ void CompositeVectors() { TestCompositeVectors(2); TestCompositeVectors(3); + TestCompositeVectors>(5); TestCompositeVectors(2); TestCompositeVectors(3); + TestCompositeVectors>(5); } } // anonymous namespace