diff --git a/src/Plugins/SimplnxCore/CMakeLists.txt b/src/Plugins/SimplnxCore/CMakeLists.txt index 703db545ed..8874f2eb02 100644 --- a/src/Plugins/SimplnxCore/CMakeLists.txt +++ b/src/Plugins/SimplnxCore/CMakeLists.txt @@ -114,6 +114,7 @@ set(FilterList ReplaceElementAttributesWithNeighborValuesFilter ResampleImageGeomFilter ResampleRectGridToImageGeomFilter + ReshapeDataArrayFilter ReverseTriangleWindingFilter RobustAutomaticThresholdFilter RotateSampleRefFrameFilter @@ -209,6 +210,7 @@ set(AlgorithmList ReplaceElementAttributesWithNeighborValues ResampleImageGeom ResampleRectGridToImageGeom + ReshapeDataArray ScalarSegmentFeatures SharedFeatureFace Silhouette diff --git a/src/Plugins/SimplnxCore/docs/ReshapeDataArrayFilter.md b/src/Plugins/SimplnxCore/docs/ReshapeDataArrayFilter.md new file mode 100644 index 0000000000..bda2154f43 --- /dev/null +++ b/src/Plugins/SimplnxCore/docs/ReshapeDataArrayFilter.md @@ -0,0 +1,23 @@ +# Reshape Data Array + +## Group (Subgroup) + +Core (Generation) + +## Description + +This **Filter** is used to modify the tuple shape of Data Arrays, Neighbor Lists, and String Arrays within a data structure. It validates the new tuple dimensions to ensure they are positive and differ from the current shape, preventing unnecessary or invalid reshapes. + +**NOTE:** If the input array is a Neighbor List or String Array, the filter will throw a warning if the new tuple dimensions are multi-dimensional. This is because these array types do not support multi-dimensional tuple dimensions and the filter will default to reshaping the data to an equivalent 1-dimensional number of tuples. + +% Auto generated parameter table will be inserted here + +## Example Pipelines + +## License & Copyright + +Please see the description file distributed with this **Plugin** + +## DREAM3D-NX Help + +If you need help, need to file a bug report or want to request a new feature, please head over to the [DREAM3DNX-Issues](https://github.com/BlueQuartzSoftware/DREAM3DNX-Issues/discussions) GitHub site where the community of DREAM3D-NX users can help answer your questions. diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ConcatenateDataArrays.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ConcatenateDataArrays.hpp index c1d12966df..421e2f4272 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ConcatenateDataArrays.hpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ConcatenateDataArrays.hpp @@ -111,12 +111,6 @@ class SIMPLNXCORE_EXPORT ConcatenateDataArrays InputArraysUnsupported = -2306 }; - // Warning Codes - enum class WarningCodes : int32 - { - MultipleTupleDimsNotSupported = -100 - }; - Result<> operator()(); const std::atomic_bool& getCancel(); diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReshapeDataArray.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReshapeDataArray.cpp new file mode 100644 index 0000000000..3c0afde002 --- /dev/null +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReshapeDataArray.cpp @@ -0,0 +1,50 @@ +#include "ReshapeDataArray.hpp" + +using namespace nx::core; + +// ----------------------------------------------------------------------------- +ReshapeDataArray::ReshapeDataArray(DataStructure& dataStructure, const IFilter::MessageHandler& mesgHandler, const std::atomic_bool& shouldCancel, ReshapeDataArrayInputValues* inputValues) +: m_DataStructure(dataStructure) +, m_InputValues(inputValues) +, m_ShouldCancel(shouldCancel) +, m_MessageHandler(mesgHandler) +{ +} + +// ----------------------------------------------------------------------------- +ReshapeDataArray::~ReshapeDataArray() noexcept = default; + +// ----------------------------------------------------------------------------- +const std::atomic_bool& ReshapeDataArray::getCancel() +{ + return m_ShouldCancel; +} + +// ----------------------------------------------------------------------------- +Result<> ReshapeDataArray::operator()() +{ + auto outputArrayPath = m_InputValues->InputArrayPath.getParent().createChildPath(fmt::format(".{}", m_InputValues->InputArrayPath.getTargetName())); + auto& outputArray = m_DataStructure.getDataRefAs(outputArrayPath); + + std::string arrayTypeName = outputArray.getTypeName(); + switch(outputArray.getArrayType()) + { + case IArray::ArrayType::DataArray: { + return ReshapeArray(m_DataStructure, m_InputValues->InputArrayPath, outputArrayPath, m_MessageHandler); + } + case IArray::ArrayType::StringArray: { + return ReshapeArrayImpl(m_DataStructure, m_InputValues->InputArrayPath, outputArrayPath, m_MessageHandler); + } + case IArray::ArrayType::NeighborListArray: { + return ReshapeNeighborList(m_DataStructure, m_InputValues->InputArrayPath, outputArrayPath, m_MessageHandler); + } + case IArray::ArrayType::Any: { + return MakeErrorResult(to_underlying(ReshapeDataArray::ErrorCodes::InputArrayEqualsAny), + "Every array in the input arrays list has array type 'Any'. This SHOULD NOT be possible, so please contact the developers."); + } + default: { + return MakeErrorResult(to_underlying(ReshapeDataArray::ErrorCodes::InputArrayUnsupported), + "Every array in the input arrays list has array type '{}'. This is an array type that is currently not supported by this filter, so please contact the developers."); + } + } +} diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReshapeDataArray.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReshapeDataArray.hpp new file mode 100644 index 0000000000..2e6613c8ce --- /dev/null +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReshapeDataArray.hpp @@ -0,0 +1,151 @@ +#pragma once + +#include "SimplnxCore/SimplnxCore_export.hpp" + +#include "simplnx/DataStructure/DataArray.hpp" +#include "simplnx/DataStructure/StringArray.hpp" +#include "simplnx/Filter/IFilter.hpp" +#include "simplnx/Parameters/DynamicTableParameter.hpp" +#include "simplnx/Utilities/DataArrayUtilities.hpp" +#include "simplnx/Utilities/FilterUtilities.hpp" + +namespace nx::core +{ +template +struct is_allowed_array_type : std::false_type +{ +}; + +template +struct is_allowed_array_type> : std::true_type +{ +}; + +template <> +struct is_allowed_array_type : std::true_type +{ +}; + +template +typename std::enable_if::value, Result<>>::type ReshapeArrayImpl(DataStructure& dataStructure, const DataPath& inputArrayPath, const DataPath& outputArrayPath, + const IFilter::MessageHandler& messageHandler) +{ + auto& inputDataArray = dataStructure.getDataRefAs(inputArrayPath); + auto& outputDataArray = dataStructure.getDataRefAs(outputArrayPath); + messageHandler({IFilter::Message::Type::Info, fmt::format("Reshaping data array '{}' from [{}] to [{}]...", inputArrayPath.toString(), fmt::join(inputDataArray.getTupleShape(), ","), + fmt::join(outputDataArray.getTupleShape(), ","))}); + usize tuplesToCopy = std::min(inputDataArray.getNumberOfTuples(), outputDataArray.getNumberOfTuples()); + auto result = CopyFromArray::CopyData(inputDataArray, outputDataArray, 0, 0, tuplesToCopy); + if(result.invalid()) + { + return result; + } + + return {}; +} + +template +Result<> ReshapeNeighborListImpl(DataStructure& dataStructure, const DataPath& inputArrayPath, const DataPath& outputArrayPath, const IFilter::MessageHandler& messageHandler) +{ + const auto& inputNeighborList = dataStructure.getDataRefAs>(inputArrayPath); + auto& outputNeighborList = dataStructure.getDataRefAs>(outputArrayPath); + + messageHandler({IFilter::Message::Type::Info, fmt::format("Reshaping neighbor list '{}' from [{}] to [{}]...", inputArrayPath.toString(), fmt::join(inputNeighborList.getTupleShape(), ","), + fmt::join(outputNeighborList.getTupleShape(), ","))}); + for(int32 listIdx = 0; listIdx < outputNeighborList.getNumberOfTuples(); ++listIdx) + { + if(listIdx < inputNeighborList.getNumberOfTuples()) + { + outputNeighborList.setList(listIdx, inputNeighborList.getList(listIdx)); + } + else + { + typename NeighborList::SharedVectorType inputList(new std::vector({})); + outputNeighborList.setList(listIdx, inputList); + } + } + + return {}; +} + +struct SIMPLNXCORE_EXPORT ReshapeDataArrayInputValues +{ + DataPath InputArrayPath; + DynamicTableParameter::ValueType TupleDims; +}; + +/** + * @class + */ +class SIMPLNXCORE_EXPORT ReshapeDataArray +{ +public: + ReshapeDataArray(DataStructure& dataStructure, const IFilter::MessageHandler& mesgHandler, const std::atomic_bool& shouldCancel, ReshapeDataArrayInputValues* inputValues); + ~ReshapeDataArray() noexcept; + + ReshapeDataArray(const ReshapeDataArray&) = delete; + ReshapeDataArray(ReshapeDataArray&&) noexcept = delete; + ReshapeDataArray& operator=(const ReshapeDataArray&) = delete; + ReshapeDataArray& operator=(ReshapeDataArray&&) noexcept = delete; + + // Error Codes + enum class ErrorCodes : int32 + { + NonPositiveTupleDimValue = -3501, + InputArrayEqualsAny = -3502, + InputArrayUnsupported = -3503, + TupleShapesEqual = -3504 + }; + + enum class WarningCodes : int32 + { + NeighborListMultipleTupleDims = -100, + StringArrayMultipleTupleDims = -101 + }; + + Result<> operator()(); + + const std::atomic_bool& getCancel(); + +private: + DataStructure& m_DataStructure; + const ReshapeDataArrayInputValues* m_InputValues = nullptr; + const std::atomic_bool& m_ShouldCancel; + const IFilter::MessageHandler& m_MessageHandler; + + struct ReshapeDataArrayTemplateImpl + { + template + void operator()(DataStructure& dataStructure, const DataPath& inputArrayPath, const DataPath& outputArrayPath, const IFilter::MessageHandler& messageHandler, Result<>& result) + { + result = ReshapeArrayImpl>(dataStructure, inputArrayPath, outputArrayPath, messageHandler); + } + }; + + struct ReshapeNeighborListTemplateImpl + { + template + void operator()(DataStructure& dataStructure, const DataPath& inputArrayPath, const DataPath& outputArrayPath, const IFilter::MessageHandler& messageHandler, Result<>& result) + { + result = ReshapeNeighborListImpl(dataStructure, inputArrayPath, outputArrayPath, messageHandler); + } + }; + + static Result<> ReshapeArray(DataStructure& dataStructure, const DataPath& inputArrayPath, const DataPath& outputArrayPath, const IFilter::MessageHandler& messageHandler) + { + const auto& outputDataArray = dataStructure.getDataRefAs(outputArrayPath); + Result<> result; + ExecuteDataFunction(ReshapeDataArrayTemplateImpl{}, outputDataArray.getDataType(), dataStructure, inputArrayPath, outputArrayPath, messageHandler, result); + return result; + } + + static Result<> ReshapeNeighborList(DataStructure& dataStructure, const DataPath& inputArrayPath, const DataPath& outputArrayPath, const IFilter::MessageHandler& messageHandler) + { + const auto& outputNeighborList = dataStructure.getDataRefAs(outputArrayPath); + Result<> result; + ExecuteNeighborFunction(ReshapeNeighborListTemplateImpl{}, outputNeighborList.getDataType(), dataStructure, inputArrayPath, outputArrayPath, messageHandler, result); + return result; + } +}; + +} // namespace nx::core diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/ReshapeDataArrayFilter.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/ReshapeDataArrayFilter.cpp new file mode 100644 index 0000000000..9649204a3f --- /dev/null +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/ReshapeDataArrayFilter.cpp @@ -0,0 +1,204 @@ +#include "ReshapeDataArrayFilter.hpp" + +#include "SimplnxCore/Filters/Algorithms/ReshapeDataArray.hpp" + +#include "simplnx/DataStructure/IDataArray.hpp" +#include "simplnx/DataStructure/INeighborList.hpp" +#include "simplnx/DataStructure/StringArray.hpp" +#include "simplnx/Filter/Actions/CreateArrayAction.hpp" +#include "simplnx/Filter/Actions/CreateNeighborListAction.hpp" +#include "simplnx/Filter/Actions/CreateStringArrayAction.hpp" +#include "simplnx/Filter/Actions/DeleteDataAction.hpp" +#include "simplnx/Filter/Actions/RenameDataAction.hpp" +#include "simplnx/Parameters/ArraySelectionParameter.hpp" +#include "simplnx/Parameters/DynamicTableParameter.hpp" + +using namespace nx::core; + +namespace nx::core +{ +//------------------------------------------------------------------------------ +std::string ReshapeDataArrayFilter::name() const +{ + return FilterTraits::name; +} + +//------------------------------------------------------------------------------ +std::string ReshapeDataArrayFilter::className() const +{ + return FilterTraits::className; +} + +//------------------------------------------------------------------------------ +Uuid ReshapeDataArrayFilter::uuid() const +{ + return FilterTraits::uuid; +} + +//------------------------------------------------------------------------------ +std::string ReshapeDataArrayFilter::humanName() const +{ + return "Reshape Data Array"; +} + +//------------------------------------------------------------------------------ +std::vector ReshapeDataArrayFilter::defaultTags() const +{ + return {className(), "Create", "Data Structure", "Data Array", "Reshape", "transform", "morph", "adjust", "change", "modify"}; +} + +//------------------------------------------------------------------------------ +Parameters ReshapeDataArrayFilter::parameters() const +{ + Parameters params; + + params.insertSeparator(Parameters::Separator{"Input Parameters"}); + params.insert(std::make_unique(k_Input_Array_Key, "Input Array", "The input array that will be reshaped.", DataPath({"Data"}), GetAllDataTypes())); + + { + DynamicTableInfo tableInfo; + tableInfo.setRowsInfo(DynamicTableInfo::StaticVectorInfo(1)); + tableInfo.setColsInfo(DynamicTableInfo::DynamicVectorInfo(1, "DIM {}")); + + params.insert(std::make_unique(k_TupleDims_Key, "New Tuple Dimensions (Slowest to Fastest Dimensions)", + "Slowest to Fastest Dimensions. Note this might be opposite displayed by an image geometry.", tableInfo)); + } + + return params; +} + +//------------------------------------------------------------------------------ +IFilter::VersionType ReshapeDataArrayFilter::parametersVersion() const +{ + return 1; +} + +//------------------------------------------------------------------------------ +IFilter::UniquePointer ReshapeDataArrayFilter::clone() const +{ + return std::make_unique(); +} + +//------------------------------------------------------------------------------ +IFilter::PreflightResult ReshapeDataArrayFilter::preflightImpl(const DataStructure& dataStructure, const Arguments& filterArgs, const MessageHandler& messageHandler, + const std::atomic_bool& shouldCancel) const +{ + auto inputArrayPath = filterArgs.value(k_Input_Array_Key); + auto tupleDimsData = filterArgs.value(k_TupleDims_Key); + + nx::core::Result resultOutputActions; + std::vector preflightUpdatedValues; + + std::vector tDims = {}; + const auto& rowData = tupleDimsData.at(0); + tDims.reserve(rowData.size()); + for(usize i = 0; i < rowData.size(); ++i) + { + auto floatValue = rowData[i]; + if(floatValue == 0) + { + return MakePreflightErrorResult(to_underlying(ReshapeDataArray::ErrorCodes::NonPositiveTupleDimValue), + fmt::format("Tuple dimension at index {0} cannot have a value of zero. Please input a positive integer at index {0}.", i)); + } + + tDims.push_back(static_cast(floatValue)); + } + + auto& inputArray = dataStructure.getDataRefAs(inputArrayPath); + auto inputArrayTupleShape = inputArray.getTupleShape(); + if(inputArrayTupleShape == tDims) + { + return MakePreflightErrorResult(to_underlying(ReshapeDataArray::ErrorCodes::TupleShapesEqual), + fmt::format("The chosen tuple shape [{}] is the same shape as the input array's tuple shape [{}]. Please choose a differing tuple shape.", fmt::join(tDims, ","), + fmt::join(inputArrayTupleShape, ","))); + } + + usize numTuples = std::accumulate(tDims.begin(), tDims.end(), static_cast(1), std::multiplies<>()); + + // Reshape the array + auto inputArrayType = inputArray.getArrayType(); + auto outputArrayPath = inputArrayPath.getParent().createChildPath(fmt::format(".{}", inputArrayPath.getTargetName())); + switch(inputArrayType) + { + case IArray::ArrayType::DataArray: { + auto& inputDataArray = dataStructure.getDataRefAs(inputArrayPath); + resultOutputActions.value().appendAction( + std::make_unique(inputDataArray.getDataType(), tDims, inputDataArray.getComponentShape(), outputArrayPath, inputDataArray.getDataFormat())); + break; + } + case IArray::ArrayType::NeighborListArray: { + auto& inputNeighborList = dataStructure.getDataRefAs(inputArrayPath); + if(tDims.size() > 1) + { + if(numTuples == inputNeighborList.getNumberOfTuples()) + { + return MakePreflightErrorResult(to_underlying(ReshapeDataArray::ErrorCodes::TupleShapesEqual), + fmt::format("The input array '{}' is a neighbor list, which does not support multiple tuple dimensions. " + "The selected tuple shape [{}] cannot be converted to [{}] because it matches the neighbor list's tuple shape ([{}]). " + "Please choose a tuple shape that results in a different number of tuples than the neighbor list.", + inputArrayPath.toString(), fmt::join(tDims, ","), numTuples, numTuples)); + } + else + { + resultOutputActions.warnings().push_back( + Warning{to_underlying(ReshapeDataArray::WarningCodes::NeighborListMultipleTupleDims), + fmt::format("The input array '{}' is a neighbor list, and neighbor lists do not support multiple tuple dimensions. The neighbor list will be reshaped to [{}] instead.", + inputArrayPath.toString(), numTuples)}); + } + } + + resultOutputActions.value().appendAction(std::make_unique(inputNeighborList.getDataType(), numTuples, outputArrayPath)); + break; + } + case IArray::ArrayType::StringArray: { + auto& inputStringArray = dataStructure.getDataRefAs(inputArrayPath); + if(tDims.size() > 1) + { + if(numTuples == inputStringArray.getNumberOfTuples()) + { + return MakePreflightErrorResult(to_underlying(ReshapeDataArray::ErrorCodes::TupleShapesEqual), + fmt::format("The input array '{}' is a string array, which does not support multiple tuple dimensions. " + "The selected tuple shape [{}] cannot be converted to [{}] because it matches the string array's tuple shape ([{}]). " + "Please choose a tuple shape that results in a different number of tuples than the string array.", + inputArrayPath.toString(), fmt::join(tDims, ","), numTuples, numTuples)); + } + else + { + resultOutputActions.warnings().push_back( + Warning{to_underlying(ReshapeDataArray::WarningCodes::StringArrayMultipleTupleDims), + fmt::format("The input array '{}' is a string array, and string arrays do not support multiple tuple dimensions. The string array will be reshaped to [{}] instead.", + inputArrayPath.toString(), numTuples)}); + } + } + + resultOutputActions.value().appendAction(std::make_unique(tDims, outputArrayPath)); + break; + } + case IArray::ArrayType::Any: { + return MakePreflightErrorResult(to_underlying(ReshapeDataArray::ErrorCodes::InputArrayEqualsAny), + fmt::format("Input array '{}' has array type 'Any'. Something has gone horribly wrong, please contact the developers.", inputArray.getName())); + } + default: { + return MakePreflightErrorResult(to_underlying(ReshapeDataArray::ErrorCodes::InputArrayUnsupported), + fmt::format("Input array '{}' has array type '{}'. This array type is not currently supported by this filter, please contact the developers.", + inputArray.getName(), inputArray.getTypeName())); + } + } + + resultOutputActions.value().appendDeferredAction(std::make_unique(inputArrayPath)); + resultOutputActions.value().appendDeferredAction(std::make_unique(outputArrayPath, inputArrayPath.getTargetName())); + + return {std::move(resultOutputActions), std::move(preflightUpdatedValues)}; +} + +//------------------------------------------------------------------------------ +Result<> ReshapeDataArrayFilter::executeImpl(DataStructure& dataStructure, const Arguments& filterArgs, const PipelineFilter* pipelineNode, const MessageHandler& messageHandler, + const std::atomic_bool& shouldCancel) const +{ + ReshapeDataArrayInputValues inputValues; + inputValues.InputArrayPath = filterArgs.value(k_Input_Array_Key); + inputValues.TupleDims = filterArgs.value(k_TupleDims_Key); + + return ReshapeDataArray(dataStructure, messageHandler, shouldCancel, &inputValues)(); +} +} // namespace nx::core diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/ReshapeDataArrayFilter.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/ReshapeDataArrayFilter.hpp new file mode 100644 index 0000000000..b3a91eb542 --- /dev/null +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/ReshapeDataArrayFilter.hpp @@ -0,0 +1,107 @@ +#pragma once + +#include "SimplnxCore/SimplnxCore_export.hpp" + +#include "simplnx/Common/StringLiteral.hpp" +#include "simplnx/Filter/FilterTraits.hpp" +#include "simplnx/Filter/IFilter.hpp" + +namespace nx::core +{ +class SIMPLNXCORE_EXPORT ReshapeDataArrayFilter : public IFilter +{ +public: + ReshapeDataArrayFilter() = default; + ~ReshapeDataArrayFilter() noexcept override = default; + + ReshapeDataArrayFilter(const ReshapeDataArrayFilter&) = delete; + ReshapeDataArrayFilter(ReshapeDataArrayFilter&&) noexcept = delete; + + ReshapeDataArrayFilter& operator=(const ReshapeDataArrayFilter&) = delete; + ReshapeDataArrayFilter& operator=(ReshapeDataArrayFilter&&) noexcept = delete; + + // Parameter Keys + static inline constexpr StringLiteral k_Input_Array_Key = "input_array_path"; + static inline constexpr StringLiteral k_TupleDims_Key = "tuple_dims"; + + /** + * @brief Reads SIMPL json and converts it simplnx Arguments. + * @param json + * @return Result + */ + static Result FromSIMPLJson(const nlohmann::json& json); + + /** + * @brief Returns the filter's name. + * @return std::string + */ + std::string name() const override; + + /** + * @brief Returns the C++ classname of this filter. + * @return std::string + */ + std::string className() const override; + + /** + * @brief Returns the filter's UUID. + * @return Uuid + */ + Uuid uuid() const override; + + /** + * @brief Returns the filter name as a human-readable string. + * @return std::string + */ + std::string humanName() const override; + + /** + * @brief Returns the default tags for this filter. + * @return + */ + std::vector defaultTags() const override; + + /** + * @brief Returns a collection of parameters required to execute the filter. + * @return Parameters + */ + Parameters parameters() const override; + + /** + * @brief Returns parameters version integer. + * Initial version should always be 1. + * Should be incremented everytime the parameters change. + * @return VersionType + */ + VersionType parametersVersion() const override; + + /** + * @brief Creates and returns a copy of the filter. + * @return UniquePointer + */ + UniquePointer clone() const override; + +protected: + /** + * @brief + * @param data + * @param filterArgs + * @param messageHandler + * @return Result + */ + PreflightResult preflightImpl(const DataStructure& dataStructure, const Arguments& filterArgs, const MessageHandler& messageHandler, const std::atomic_bool& shouldCancel) const override; + + /** + * @brief + * @param dataStructure + * @param args + * @param pipelineNode + * @param messageHandler + * @return Result<> + */ + Result<> executeImpl(DataStructure& dataStructure, const Arguments& args, const PipelineFilter* pipelineNode, const MessageHandler& messageHandler, + const std::atomic_bool& shouldCancel) const override; +}; +} // namespace nx::core + +SIMPLNX_DEF_FILTER_TRAITS(nx::core, ReshapeDataArrayFilter, "9831f05e-8b35-4c3d-b733-8c0a24bbd5ca"); diff --git a/src/Plugins/SimplnxCore/test/CMakeLists.txt b/src/Plugins/SimplnxCore/test/CMakeLists.txt index 66dc21ad7d..d382927785 100644 --- a/src/Plugins/SimplnxCore/test/CMakeLists.txt +++ b/src/Plugins/SimplnxCore/test/CMakeLists.txt @@ -115,6 +115,7 @@ set(${PLUGIN_NAME}UnitTest_SRCS ReplaceElementAttributesWithNeighborValuesTest.cpp ResampleImageGeomTest.cpp ResampleRectGridToImageGeomTest.cpp + ReshapeDataArrayTest.cpp ReverseTriangleWindingTest.cpp RobustAutomaticThresholdTest.cpp RotateSampleRefFrameTest.cpp diff --git a/src/Plugins/SimplnxCore/test/ReshapeDataArrayTest.cpp b/src/Plugins/SimplnxCore/test/ReshapeDataArrayTest.cpp new file mode 100644 index 0000000000..f34504afc8 --- /dev/null +++ b/src/Plugins/SimplnxCore/test/ReshapeDataArrayTest.cpp @@ -0,0 +1,399 @@ +#include "SimplnxCore/Filters/Algorithms/ReshapeDataArray.hpp" +#include "SimplnxCore/Filters/ReshapeDataArrayFilter.hpp" +#include "SimplnxCore/SimplnxCore_test_dirs.hpp" + +#include "simplnx/Parameters/DynamicTableParameter.hpp" +#include "simplnx/UnitTest/UnitTestCommon.hpp" + +#include + +using namespace nx::core; + +namespace +{ +const std::string k_TestArrayName = "TestArray"; +const std::string k_TestResultArrayName = "TestResultArray"; +const DataPath k_InputArrayPath = DataPath({k_TestArrayName}); +} // namespace + +TEMPLATE_TEST_CASE("SimplnxCore::ReshapeDataArraysFilter: Valid DataArrays - Same Size", "[SimplnxCore][ConcatenateDataArraysFilter]", bool, int8, int16, int32, int64, uint8, uint16, uint32, uint64, + float32, float64) +{ + using T = TestType; + + ReshapeDataArrayFilter filter; + DataStructure dataStructure; + Arguments args; + + auto array1 = DataArray::template CreateWithStore>(dataStructure, k_TestArrayName, std::vector{4}, std::vector{2}); + array1->setComponent(0, 0, 0); + array1->setComponent(0, 1, 1); + array1->setComponent(1, 0, 0); + array1->setComponent(1, 1, 0); + array1->setComponent(2, 0, 1); + array1->setComponent(2, 1, 0); + array1->setComponent(3, 0, 1); + array1->setComponent(3, 1, 0); + + args.insert(ReshapeDataArrayFilter::k_Input_Array_Key, std::make_any(k_InputArrayPath)); + args.insert(ReshapeDataArrayFilter::k_TupleDims_Key, std::make_any(DynamicTableInfo::TableDataType{{2.0, 2.0}})); + + auto result = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(result.result); + + REQUIRE_NOTHROW(dataStructure.getDataRefAs>(k_InputArrayPath)); + auto reshapedArray = dataStructure.getDataRefAs>(k_InputArrayPath); + + REQUIRE(reshapedArray.getTupleShape() == std::vector{2, 2}); + REQUIRE(reshapedArray.getNumberOfComponents() == 2); + REQUIRE(reshapedArray[0] == 0); + REQUIRE(reshapedArray[1] == 1); + REQUIRE(reshapedArray[2] == 0); + REQUIRE(reshapedArray[3] == 0); + REQUIRE(reshapedArray[4] == 1); + REQUIRE(reshapedArray[5] == 0); + REQUIRE(reshapedArray[6] == 1); + REQUIRE(reshapedArray[7] == 0); +} + +TEMPLATE_TEST_CASE("SimplnxCore::ReshapeDataArraysFilter: Valid DataArrays - Shrink", "[SimplnxCore][ConcatenateDataArraysFilter]", bool, int8, int16, int32, int64, uint8, uint16, uint32, uint64, + float32, float64) +{ + using T = TestType; + + ReshapeDataArrayFilter filter; + DataStructure dataStructure; + Arguments args; + + auto array1 = DataArray::template CreateWithStore>(dataStructure, k_TestArrayName, std::vector{3, 2}, std::vector{2}); + array1->setComponent(0, 0, 0); + array1->setComponent(0, 1, 1); + array1->setComponent(1, 0, 0); + array1->setComponent(1, 1, 0); + array1->setComponent(2, 0, 1); + array1->setComponent(2, 1, 0); + array1->setComponent(3, 0, 1); + array1->setComponent(3, 1, 0); + array1->setComponent(4, 0, 1); + array1->setComponent(4, 1, 0); + array1->setComponent(5, 0, 1); + array1->setComponent(5, 1, 0); + + args.insert(ReshapeDataArrayFilter::k_Input_Array_Key, std::make_any(k_InputArrayPath)); + args.insert(ReshapeDataArrayFilter::k_TupleDims_Key, std::make_any(DynamicTableInfo::TableDataType{{2.0, 2.0}})); + + auto result = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(result.result); + + REQUIRE_NOTHROW(dataStructure.getDataRefAs>(k_InputArrayPath)); + auto reshapedArray = dataStructure.getDataRefAs>(k_InputArrayPath); + + REQUIRE(reshapedArray.getTupleShape() == std::vector{2, 2}); + REQUIRE(reshapedArray.getNumberOfComponents() == 2); + REQUIRE(reshapedArray[0] == 0); + REQUIRE(reshapedArray[1] == 1); + REQUIRE(reshapedArray[2] == 0); + REQUIRE(reshapedArray[3] == 0); + REQUIRE(reshapedArray[4] == 1); + REQUIRE(reshapedArray[5] == 0); + REQUIRE(reshapedArray[6] == 1); + REQUIRE(reshapedArray[7] == 0); +} + +TEMPLATE_TEST_CASE("SimplnxCore::ReshapeDataArraysFilter: Valid DataArrays - Expand", "[SimplnxCore][ConcatenateDataArraysFilter]", bool, int8, int16, int32, int64, uint8, uint16, uint32, uint64, + float32, float64) +{ + using T = TestType; + + ReshapeDataArrayFilter filter; + DataStructure dataStructure; + Arguments args; + + auto array1 = DataArray::template CreateWithStore>(dataStructure, k_TestArrayName, std::vector{1}, std::vector{2}); + array1->setComponent(0, 0, 0); + array1->setComponent(0, 1, 1); + + args.insert(ReshapeDataArrayFilter::k_Input_Array_Key, std::make_any(k_InputArrayPath)); + args.insert(ReshapeDataArrayFilter::k_TupleDims_Key, std::make_any(DynamicTableInfo::TableDataType{{2.0, 2.0}})); + + auto result = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(result.result); + + REQUIRE_NOTHROW(dataStructure.getDataRefAs>(k_InputArrayPath)); + auto reshapedArray = dataStructure.getDataRefAs>(k_InputArrayPath); + + REQUIRE(reshapedArray.getTupleShape() == std::vector{2, 2}); + REQUIRE(reshapedArray.getNumberOfComponents() == 2); + REQUIRE(reshapedArray[0] == 0); + REQUIRE(reshapedArray[1] == 1); + REQUIRE(reshapedArray[2] == 0); + REQUIRE(reshapedArray[3] == 0); + REQUIRE(reshapedArray[4] == 0); + REQUIRE(reshapedArray[5] == 0); + REQUIRE(reshapedArray[6] == 0); + REQUIRE(reshapedArray[7] == 0); +} + +TEMPLATE_TEST_CASE("SimplnxCore::ReshapeDataArraysFilter: Valid NeighborLists - Shrink", "[SimplnxCore][ConcatenateDataArraysFilter]", int8, int16, int32, int64, uint8, uint16, uint32, uint64, + float32, float64) +{ + using T = TestType; + + ReshapeDataArrayFilter filter; + DataStructure dataStructure; + Arguments args; + + auto inputNeighborList = NeighborList::Create(dataStructure, k_TestArrayName, 2); + typename NeighborList::SharedVectorType inputList(new std::vector({0, 1, 0})); + inputNeighborList->setList(0, inputList); + typename NeighborList::SharedVectorType inputList2(new std::vector({1, 0, 0})); + inputNeighborList->setList(1, inputList2); + + args.insert(ReshapeDataArrayFilter::k_Input_Array_Key, std::make_any(k_InputArrayPath)); + args.insert(ReshapeDataArrayFilter::k_TupleDims_Key, std::make_any(DynamicTableInfo::TableDataType{{1.0}})); + + auto result = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(result.result); + + REQUIRE_NOTHROW(dataStructure.getDataRefAs>(k_InputArrayPath)); + auto reshapedNeighborList = dataStructure.getDataRefAs>(k_InputArrayPath); + + REQUIRE(reshapedNeighborList.getTupleShape() == std::vector{1}); + auto reshapedList1 = reshapedNeighborList.getList(0); + + REQUIRE(*reshapedList1 == *inputList); +} + +TEMPLATE_TEST_CASE("SimplnxCore::ReshapeDataArraysFilter: Valid NeighborLists - Expand", "[SimplnxCore][ConcatenateDataArraysFilter]", int8, int16, int32, int64, uint8, uint16, uint32, uint64, + float32, float64) +{ + using T = TestType; + + ReshapeDataArrayFilter filter; + DataStructure dataStructure; + Arguments args; + + auto inputNeighborList = NeighborList::Create(dataStructure, k_TestArrayName, 1); + typename NeighborList::SharedVectorType inputList(new std::vector({0, 1, 0})); + inputNeighborList->setList(0, inputList); + + args.insert(ReshapeDataArrayFilter::k_Input_Array_Key, std::make_any(k_InputArrayPath)); + args.insert(ReshapeDataArrayFilter::k_TupleDims_Key, std::make_any(DynamicTableInfo::TableDataType{{2.0}})); + + auto result = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(result.result); + + REQUIRE_NOTHROW(dataStructure.getDataRefAs>(k_InputArrayPath)); + auto reshapedNeighborList = dataStructure.getDataRefAs>(k_InputArrayPath); + + REQUIRE(reshapedNeighborList.getTupleShape() == std::vector{2}); + auto reshapedList1 = reshapedNeighborList.getList(0); + auto reshapedList2 = reshapedNeighborList.getList(1); + + REQUIRE(*reshapedList1 == *inputList); + REQUIRE((*reshapedList2).empty()); +} + +TEMPLATE_TEST_CASE("SimplnxCore::ReshapeDataArraysFilter: Valid StringArrays - Shrink", "[SimplnxCore][ConcatenateDataArraysFilter]", bool, int8, int16, int32, int64, uint8, uint16, uint32, uint64, + float32, float64) +{ + using T = TestType; + + ReshapeDataArrayFilter filter; + DataStructure dataStructure; + Arguments args; + + std::string value1 = "Foo"; + std::string value2 = "Bar"; + std::string value3 = "Baz"; + std::string value4 = "Fizz"; + std::string value5 = "Buzz"; + std::string value6 = "FizzBuzz"; + + StringArray::CreateWithValues(dataStructure, k_TestArrayName, {value1, value2, value3, value4, value5, value6}); + + args.insert(ReshapeDataArrayFilter::k_Input_Array_Key, std::make_any(k_InputArrayPath)); + args.insert(ReshapeDataArrayFilter::k_TupleDims_Key, std::make_any(DynamicTableInfo::TableDataType{{4.0}})); + + auto result = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(result.result); + + REQUIRE_NOTHROW(dataStructure.getDataRefAs(k_InputArrayPath)); + auto reshapedArray = dataStructure.getDataRefAs(k_InputArrayPath); + + REQUIRE(reshapedArray.getTupleShape() == std::vector{4}); + REQUIRE(reshapedArray[0] == value1); + REQUIRE(reshapedArray[1] == value2); + REQUIRE(reshapedArray[2] == value3); + REQUIRE(reshapedArray[3] == value4); +} + +TEMPLATE_TEST_CASE("SimplnxCore::ReshapeDataArraysFilter: Valid StringArrays - Expand", "[SimplnxCore][ConcatenateDataArraysFilter]", bool, int8, int16, int32, int64, uint8, uint16, uint32, uint64, + float32, float64) +{ + using T = TestType; + + ReshapeDataArrayFilter filter; + DataStructure dataStructure; + Arguments args; + + std::string value1 = "Foo"; + std::string value2 = "Bar"; + + StringArray::CreateWithValues(dataStructure, k_TestArrayName, {value1, value2}); + + args.insert(ReshapeDataArrayFilter::k_Input_Array_Key, std::make_any(k_InputArrayPath)); + args.insert(ReshapeDataArrayFilter::k_TupleDims_Key, std::make_any(DynamicTableInfo::TableDataType{{4.0}})); + + auto result = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(result.result); + + REQUIRE_NOTHROW(dataStructure.getDataRefAs(k_InputArrayPath)); + auto reshapedArray = dataStructure.getDataRefAs(k_InputArrayPath); + + REQUIRE(reshapedArray.getTupleShape() == std::vector{4}); + REQUIRE(reshapedArray[0] == value1); + REQUIRE(reshapedArray[1] == value2); + REQUIRE(reshapedArray[2].empty()); + REQUIRE(reshapedArray[3].empty()); +} + +TEMPLATE_TEST_CASE("SimplnxCore::ReshapeDataArraysFilter: Invalid - Same Size", "[SimplnxCore][ConcatenateDataArraysFilter]", int8, int16, int32, int64, uint8, uint16, uint32, uint64, float32, + float64) +{ + using T = TestType; + + ReshapeDataArrayFilter filter; + DataStructure dataStructure; + Arguments args; + + SECTION("DataArrays") + { + auto array1 = DataArray::template CreateWithStore>(dataStructure, k_TestArrayName, std::vector{2, 2}, std::vector{2}); + array1->setComponent(0, 0, 0); + array1->setComponent(0, 1, 1); + array1->setComponent(1, 0, 0); + array1->setComponent(1, 1, 0); + array1->setComponent(2, 0, 1); + array1->setComponent(2, 1, 0); + array1->setComponent(3, 0, 1); + array1->setComponent(3, 1, 0); + + args.insert(ReshapeDataArrayFilter::k_Input_Array_Key, std::make_any(k_InputArrayPath)); + args.insert(ReshapeDataArrayFilter::k_TupleDims_Key, std::make_any(DynamicTableInfo::TableDataType{{2.0, 2.0}})); + } + SECTION("NeighborLists") + { + auto inputNeighborList = NeighborList::Create(dataStructure, k_TestArrayName, 1); + typename NeighborList::SharedVectorType inputList(new std::vector({0, 1, 0})); + inputNeighborList->setList(0, inputList); + + args.insert(ReshapeDataArrayFilter::k_Input_Array_Key, std::make_any(k_InputArrayPath)); + args.insert(ReshapeDataArrayFilter::k_TupleDims_Key, std::make_any(DynamicTableInfo::TableDataType{{1.0}})); + } + SECTION("StringArrays") + { + std::string value1 = "Foo"; + std::string value2 = "Bar"; + + StringArray::CreateWithValues(dataStructure, k_TestArrayName, {value1, value2}); + + args.insert(ReshapeDataArrayFilter::k_Input_Array_Key, std::make_any(k_InputArrayPath)); + args.insert(ReshapeDataArrayFilter::k_TupleDims_Key, std::make_any(DynamicTableInfo::TableDataType{{2.0}})); + } + + auto result = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_INVALID(result.result); + REQUIRE(result.result.errors().size() == 1); + REQUIRE(result.result.errors()[0].code == to_underlying(ReshapeDataArray::ErrorCodes::TupleShapesEqual)); +} + +TEMPLATE_TEST_CASE("SimplnxCore::ReshapeDataArraysFilter: NeighborList Warning - Expand", "[SimplnxCore][ConcatenateDataArraysFilter]", int8, int16, int32, int64, uint8, uint16, uint32, uint64, + float32, float64) +{ + using T = TestType; + + ReshapeDataArrayFilter filter; + DataStructure dataStructure; + Arguments args; + + auto inputNeighborList = NeighborList::Create(dataStructure, k_TestArrayName, 1); + typename NeighborList::SharedVectorType inputList(new std::vector({0, 1, 0})); + inputNeighborList->setList(0, inputList); + + args.insert(ReshapeDataArrayFilter::k_Input_Array_Key, std::make_any(k_InputArrayPath)); + args.insert(ReshapeDataArrayFilter::k_TupleDims_Key, std::make_any(DynamicTableInfo::TableDataType{{2.0, 2.0}})); + + auto result = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(result.result); + REQUIRE(result.result.warnings().size() == 1); + REQUIRE(result.result.warnings()[0].code == to_underlying(ReshapeDataArray::WarningCodes::NeighborListMultipleTupleDims)); + + REQUIRE_NOTHROW(dataStructure.getDataRefAs>(k_InputArrayPath)); + auto reshapedNeighborList = dataStructure.getDataRefAs>(k_InputArrayPath); + + REQUIRE(reshapedNeighborList.getTupleShape() == std::vector{4}); + auto reshapedList1 = reshapedNeighborList.getList(0); + auto reshapedList2 = reshapedNeighborList.getList(1); + auto reshapedList3 = reshapedNeighborList.getList(2); + auto reshapedList4 = reshapedNeighborList.getList(3); + + REQUIRE(*reshapedList1 == *inputList); + REQUIRE((*reshapedList2).empty()); + REQUIRE((*reshapedList3).empty()); + REQUIRE((*reshapedList4).empty()); +} + +TEMPLATE_TEST_CASE("SimplnxCore::ReshapeDataArraysFilter: StringArray Warning - Expand", "[SimplnxCore][ConcatenateDataArraysFilter]", int8, int16, int32, int64, uint8, uint16, uint32, uint64, + float32, float64) +{ + using T = TestType; + + ReshapeDataArrayFilter filter; + DataStructure dataStructure; + Arguments args; + + std::string value1 = "Foo"; + std::string value2 = "Bar"; + + StringArray::CreateWithValues(dataStructure, k_TestArrayName, {value1, value2}); + + args.insert(ReshapeDataArrayFilter::k_Input_Array_Key, std::make_any(k_InputArrayPath)); + args.insert(ReshapeDataArrayFilter::k_TupleDims_Key, std::make_any(DynamicTableInfo::TableDataType{{2.0, 3.0}})); + + auto result = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(result.result); + REQUIRE(result.result.warnings().size() == 1); + REQUIRE(result.result.warnings()[0].code == to_underlying(ReshapeDataArray::WarningCodes::StringArrayMultipleTupleDims)); + + REQUIRE_NOTHROW(dataStructure.getDataRefAs(k_InputArrayPath)); + auto reshapedArray = dataStructure.getDataRefAs(k_InputArrayPath); + + REQUIRE(reshapedArray.getTupleShape() == std::vector{6}); + REQUIRE(reshapedArray[0] == value1); + REQUIRE(reshapedArray[1] == value2); + REQUIRE(reshapedArray[2].empty()); + REQUIRE(reshapedArray[3].empty()); + REQUIRE(reshapedArray[4].empty()); + REQUIRE(reshapedArray[5].empty()); +} + +TEST_CASE("SimplnxCore::ReshapeDataArraysFilter: Invalid Tuple Dimensions", "[SimplnxCore][ConcatenateDataArraysFilter]") +{ + ReshapeDataArrayFilter filter; + DataStructure dataStructure; + Arguments args; + + std::string value1 = "Foo"; + std::string value2 = "Bar"; + + StringArray::CreateWithValues(dataStructure, k_TestArrayName, {value1, value2}); + + args.insert(ReshapeDataArrayFilter::k_Input_Array_Key, std::make_any(k_InputArrayPath)); + args.insert(ReshapeDataArrayFilter::k_TupleDims_Key, std::make_any(DynamicTableInfo::TableDataType{{0.0, 3.0}})); + + auto result = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_INVALID(result.result); + REQUIRE(result.result.errors().size() == 1); + REQUIRE(result.result.errors()[0].code == to_underlying(ReshapeDataArray::ErrorCodes::NonPositiveTupleDimValue)); +} \ No newline at end of file