diff --git a/vtkm/filter/CMakeLists.txt b/vtkm/filter/CMakeLists.txt index 2b56dc0d3..977af9df4 100644 --- a/vtkm/filter/CMakeLists.txt +++ b/vtkm/filter/CMakeLists.txt @@ -45,6 +45,7 @@ set(headers ImageMedian.h Lagrangian.h LagrangianStructures.h + MapFieldMergeAverage.h MapFieldPermutation.h Mask.h MaskPoints.h @@ -155,6 +156,7 @@ set(sources_device GradientScalar.cxx GradientUniformPoints.cxx GradientVector.cxx + MapFieldMergeAverage.cxx MapFieldPermutation.cxx PointAverage.cxx Threshold.cxx diff --git a/vtkm/filter/MapFieldMergeAverage.cxx b/vtkm/filter/MapFieldMergeAverage.cxx new file mode 100644 index 000000000..c275ae8df --- /dev/null +++ b/vtkm/filter/MapFieldMergeAverage.cxx @@ -0,0 +1,69 @@ +//============================================================================ +// 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 + +#include + +#include + +#include + +namespace +{ + +struct DoMapFieldMerge +{ + bool CalledMap = false; + + template + void operator()(const vtkm::cont::ArrayHandle& inputArray, + const vtkm::worklet::internal::KeysBase& keys, + vtkm::cont::VariantArrayHandle& output) + { + vtkm::cont::ArrayHandle outputArray = vtkm::worklet::AverageByKey::Run(keys, inputArray); + output = vtkm::cont::VariantArrayHandle(outputArray); + this->CalledMap = true; + } +}; + +} // anonymous namespace + +bool vtkm::filter::MapFieldMergeAverage(const vtkm::cont::Field& inputField, + const vtkm::worklet::internal::KeysBase& keys, + vtkm::cont::Field& outputField) +{ + vtkm::cont::VariantArrayHandle outputArray; + DoMapFieldMerge functor; + inputField.GetData().ResetTypes().CastAndCall( + vtkm::filter::PolicyDefault::StorageList{}, functor, keys, outputArray); + if (functor.CalledMap) + { + outputField = vtkm::cont::Field(inputField.GetName(), inputField.GetAssociation(), outputArray); + } + else + { + VTKM_LOG_S(vtkm::cont::LogLevel::Warn, "Faild to map field " << inputField.GetName()); + } + return functor.CalledMap; +} + +bool vtkm::filter::MapFieldMergeAverage(const vtkm::cont::Field& inputField, + const vtkm::worklet::internal::KeysBase& keys, + vtkm::cont::DataSet& outputData) +{ + vtkm::cont::Field outputField; + bool success = vtkm::filter::MapFieldMergeAverage(inputField, keys, outputField); + if (success) + { + outputData.AddField(outputField); + } + return success; +} diff --git a/vtkm/filter/MapFieldMergeAverage.h b/vtkm/filter/MapFieldMergeAverage.h new file mode 100644 index 000000000..29e5ad579 --- /dev/null +++ b/vtkm/filter/MapFieldMergeAverage.h @@ -0,0 +1,73 @@ +//============================================================================ +// 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. +//============================================================================ + +#ifndef vtk_m_filter_MapFieldMergeAverage_h +#define vtk_m_filter_MapFieldMergeAverage_h + +#include +#include +#include + +#include + +#include + +namespace vtkm +{ +namespace filter +{ + +/// \brief Maps a field by merging entries based on a keys object. +/// +/// This method will create a new field containing the data from the provided `inputField` but but +/// with groups of entities merged together. The input `keys` object encapsulates which elements +/// should be merged together. A group of elements merged together will be averaged. The result is +/// placed in `outputField`. +/// +/// The intention of this method is to implement the `MapFieldOntoOutput` methods in filters (many +/// of which require this merge 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. +/// +/// This function returns whether the field was successfully merged. If the returned result is +/// `true`, then the results in `outputField` are valid. If it is `false`, then `outputField` +/// should not be used. +/// +VTKM_FILTER_EXPORT VTKM_CONT bool MapFieldMergeAverage( + const vtkm::cont::Field& inputField, + const vtkm::worklet::internal::KeysBase& keys, + vtkm::cont::Field& outputField); + +/// \brief Maps a field by merging entries based on a keys object. +/// +/// This method will create a new field containing the data from the provided `inputField` but but +/// with groups of entities merged together. The input `keys` object encapsulates which elements +/// should be merged together. A group of elements merged together will be averaged. +/// +/// The intention of this method is to implement the `MapFieldOntoOutput` methods in filters (many +/// of which require this merge of a field), but can be used in other places as well. The +/// resulting field is put in the given `DataSet`. +/// +/// The returned `Field` has the same metadata as the input. If the metadata needs to change (such +/// as the name or the association), then a different form of `MapFieldMergeAverage` should be used. +/// +/// This function returns whether the field was successfully merged. If the returned result is +/// `true`, then `outputData` has the merged field. If it is `false`, then the field is not +/// placed in `outputData`. +/// +VTKM_FILTER_EXPORT VTKM_CONT bool MapFieldMergeAverage( + const vtkm::cont::Field& inputField, + const vtkm::worklet::internal::KeysBase& keys, + vtkm::cont::DataSet& outputData); +} +} // namespace vtkm::filter + +#endif //vtk_m_filter_MapFieldMergeAverage_h diff --git a/vtkm/filter/testing/CMakeLists.txt b/vtkm/filter/testing/CMakeLists.txt index f34cf59bc..d1181398a 100644 --- a/vtkm/filter/testing/CMakeLists.txt +++ b/vtkm/filter/testing/CMakeLists.txt @@ -39,6 +39,7 @@ set(unit_tests UnitTestImageMedianFilter.cxx UnitTestLagrangianFilter.cxx UnitTestLagrangianStructuresFilter.cxx + UnitTestMapFieldMergeAverage.cxx UnitTestMapFieldPermutation.cxx UnitTestMaskFilter.cxx UnitTestMaskPointsFilter.cxx diff --git a/vtkm/filter/testing/UnitTestMapFieldMergeAverage.cxx b/vtkm/filter/testing/UnitTestMapFieldMergeAverage.cxx new file mode 100644 index 000000000..b034f107a --- /dev/null +++ b/vtkm/filter/testing/UnitTestMapFieldMergeAverage.cxx @@ -0,0 +1,151 @@ +//============================================================================ +// 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 + +#include +#include +#include +#include +#include + +#include + +namespace +{ + +constexpr vtkm::Id ARRAY_SIZE = 26; +constexpr vtkm::Id3 ARRAY3_DIM = { 3, 3, 3 }; +constexpr vtkm::Id REDUCED_SIZE = 7; + +vtkm::worklet::Keys MakeKeys(vtkm::Id originalArraySize) +{ + vtkm::cont::ArrayHandle keyArray; + keyArray.Allocate(originalArraySize); + { + auto portal = keyArray.WritePortal(); + for (vtkm::Id i = 0; i < originalArraySize; ++i) + { + portal.Set(i, i % REDUCED_SIZE); + } + } + + return vtkm::worklet::Keys(keyArray); +} + +// Make an array of the expected output of mapping the given array using the keys returned from +// MakeKeys but with a different mechanism. +template +vtkm::cont::ArrayHandle MakeExpectedOutput(const vtkm::cont::ArrayHandle& inputArray) +{ + using ComponentType = typename vtkm::VecTraits::ComponentType; + + auto inputPortal = inputArray.ReadPortal(); + + vtkm::cont::ArrayHandle outputArray; + outputArray.Allocate(REDUCED_SIZE); + auto outputPortal = outputArray.WritePortal(); + + for (vtkm::Id reducedI = 0; reducedI < REDUCED_SIZE; ++reducedI) + { + T sum = vtkm::TypeTraits::ZeroInitialization(); + ComponentType num = 0; + for (vtkm::Id fullI = reducedI; fullI < inputArray.GetNumberOfValues(); fullI += REDUCED_SIZE) + { + sum = sum + inputPortal.Get(fullI); + num = num + ComponentType(1); + } + outputPortal.Set(reducedI, sum / T(num)); + } + + return outputArray; +} + +template +void TryArray(const vtkm::cont::ArrayHandle& inputArray) +{ + std::cout << "Input" << std::endl; + vtkm::cont::printSummary_ArrayHandle(inputArray, std::cout); + + vtkm::cont::Field::Association association = + ((sizeof(T) < 8) ? vtkm::cont::Field::Association::POINTS + : vtkm::cont::Field::Association::CELL_SET); + + vtkm::cont::Field inputField("my-array", association, inputArray); + + vtkm::worklet::Keys keys = MakeKeys(inputArray.GetNumberOfValues()); + + vtkm::cont::ArrayHandle expectedOutputArray = MakeExpectedOutput(inputArray); + std::cout << "Expected output" << std::endl; + vtkm::cont::printSummary_ArrayHandle(expectedOutputArray, std::cout); + + vtkm::cont::Field outputField; + bool result = vtkm::filter::MapFieldMergeAverage(inputField, keys, outputField); + VTKM_TEST_ASSERT(result, "Could not map the array."); + + VTKM_TEST_ASSERT(outputField.GetAssociation() == association); + VTKM_TEST_ASSERT(outputField.GetName() == "my-array"); + + vtkm::cont::ArrayHandle outputArray; + outputField.GetData().CopyTo(outputArray); + std::cout << "Actual output" << std::endl; + vtkm::cont::printSummary_ArrayHandle(outputArray, std::cout); + + VTKM_TEST_ASSERT(test_equal_portals(expectedOutputArray.ReadPortal(), outputArray.ReadPortal())); +} + +template +void TryType(T) +{ + vtkm::cont::ArrayHandle inputArray; + inputArray.Allocate(ARRAY_SIZE); + SetPortal(inputArray.WritePortal()); + TryArray(inputArray); +} + +struct TryTypeFunctor +{ + template + void operator()(T x) const + { + TryType(x); + } +}; + +void TryCartesianProduct() +{ + vtkm::cont::ArrayHandle axes[3]; + for (vtkm::IdComponent i = 0; i < 3; ++i) + { + axes[i].Allocate(ARRAY3_DIM[i]); + SetPortal(axes[i].WritePortal()); + } + + TryArray(vtkm::cont::make_ArrayHandleCartesianProduct(axes[0], axes[1], axes[2])); +} + +void DoTest() +{ + std::cout << "**** Test Basic Arrays *****" << std::endl; + vtkm::testing::Testing::TryTypes(TryTypeFunctor{}); + + std::cout << std::endl << "**** Test Uniform Point Coordiantes *****" << std::endl; + TryArray(vtkm::cont::ArrayHandleUniformPointCoordinates(ARRAY3_DIM)); + + std::cout << std::endl << "**** Test Cartesian Product *****" << std::endl; + TryCartesianProduct(); +} + +} // anonymous namespace + +int UnitTestMapFieldMergeAverage(int argc, char* argv[]) +{ + return vtkm::cont::testing::Testing::Run(DoTest, argc, argv); +}