diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/MultiThresholdObjects.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/MultiThresholdObjects.cpp index 48ea54b9d7..c606f58230 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/MultiThresholdObjects.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/MultiThresholdObjects.cpp @@ -3,6 +3,7 @@ #include "simplnx/Common/TypeTraits.hpp" #include "simplnx/DataStructure/DataArray.hpp" #include "simplnx/Utilities/ArrayThreshold.hpp" +#include "simplnx/Utilities/DataStoreUtilities.hpp" #include "simplnx/Utilities/FilterUtilities.hpp" #include @@ -11,11 +12,67 @@ using namespace nx::core; namespace { +/** + * @brief InsertThreshold + * @param numItems + * @param currentArrayPtr + * @param unionOperator + * @param newArrayPtr + * @param inverse + */ +template +void InsertThreshold(AbstractDataStore& currentVector, nx::core::IArrayThreshold::UnionOperator unionOperator, AbstractDataStore& newVector, bool inverse, T trueValue, T falseValue) +{ + usize numItems = currentVector.getNumberOfTuples(); + + for(usize i = 0; i < numItems; i++) + { + // invert the current comparison if necessary + if(inverse) + { + newVector[i] = (newVector[i] == trueValue) ? falseValue : trueValue; + } + + if(nx::core::IArrayThreshold::UnionOperator::Or == unionOperator) + { + currentVector[i] = (currentVector[i] == trueValue || newVector[i] == trueValue) ? trueValue : falseValue; + } + else if(currentVector[i] == falseValue || newVector[i] == falseValue) + { + currentVector[i] = falseValue; + } + } +} + +/** + * @brief Consolidate all assignment calls to a single method to prevent unintended diverging behavior. + * @param arrayThreshold Current threshold to pull settings from. + * @param outputResultStore Output DataStore for the current ThresholdSet. + * @param inputThresholdStore Resulting output for the target array threshold. + * @param replaceInput The first threshould in every set has its output applied to the output regardless of union operator. + * @param trueValue Output mask value when the threshold is satisfied. + * @param falseValue Output mask value when the threshold is not satisfied. + */ +template +void ApplyThresholdValues(const IArrayThreshold& arrayThreshold, AbstractDataStore& outputResultStore, AbstractDataStore& inputThresholdStore, bool replaceInput, T trueValue, T falseValue) +{ + auto unionOperator = arrayThreshold.getUnionOperator(); + bool inverse = arrayThreshold.isInverted(); + + if(replaceInput) + { + unionOperator = IArrayThreshold::UnionOperator::Or; + } + + // insert into current threshold + InsertThreshold(outputResultStore, unionOperator, inputThresholdStore, inverse, trueValue, falseValue); +} + template class ThresholdFilterHelper { public: - ThresholdFilterHelper(ArrayThreshold::ComparisonType compType, ArrayThreshold::ComparisonValue compValue, usize componentIndex, std::vector& output) + ThresholdFilterHelper(ArrayThreshold::ComparisonType compType, ArrayThreshold::ComparisonValue compValue, usize componentIndex, AbstractDataStore& output) : m_ComparisonOperator(compType) , m_ComparisonValue(compValue) , m_ComponentIndex(componentIndex) @@ -31,7 +88,8 @@ class ThresholdFilterHelper for(size_t tupleIndex = 0; tupleIndex < numTuples; ++tupleIndex) { T inputValue = m_Input.getComponentValue(tupleIndex, m_ComponentIndex); - T outputValue = CompT{}(inputValue, value) ? trueValue : falseValue; + bool comparison = CompT{}(inputValue, value); + T outputValue = comparison ? trueValue : falseValue; m_Output[tupleIndex] = outputValue; } } @@ -66,7 +124,7 @@ class ThresholdFilterHelper ArrayThreshold::ComparisonType m_ComparisonOperator; ArrayThreshold::ComparisonValue m_ComparisonValue; usize m_ComponentIndex = 0; - std::vector& m_Output; + AbstractDataStore& m_Output; }; struct ExecuteThresholdHelper @@ -79,43 +137,14 @@ struct ExecuteThresholdHelper } }; -/** - * @brief InsertThreshold - * @param numItems - * @param currentArrayPtr - * @param unionOperator - * @param newArrayPtr - * @param inverse - */ -template -void InsertThreshold(usize numItems, AbstractDataStore& currentStore, nx::core::IArrayThreshold::UnionOperator unionOperator, std::vector& newArrayPtr, bool inverse, T trueValue, T falseValue) -{ - for(usize i = 0; i < numItems; i++) - { - // invert the current comparison if necessary - if(inverse) - { - newArrayPtr[i] = (newArrayPtr[i] == trueValue) ? falseValue : trueValue; - } - - if(nx::core::IArrayThreshold::UnionOperator::Or == unionOperator) - { - currentStore[i] = (currentStore[i] == trueValue || newArrayPtr[i] == trueValue) ? trueValue : falseValue; - } - else if(currentStore[i] == falseValue || newArrayPtr[i] == falseValue) - { - currentStore[i] = falseValue; - } - } -} - template -void ThresholdValue(const ArrayThreshold& comparisonValue, const DataStructure& dataStructure, AbstractDataStore& outputResultStore, int32_t& err, bool replaceInput, bool inverse, T trueValue, - T falseValue) +void ThresholdValue(const ArrayThreshold& comparisonValue, const DataStructure& dataStructure, AbstractDataStore& outputResultVector, int32_t& err, bool replaceInput, T trueValue, T falseValue) { // Get the total number of tuples, create and initialize an array with FALSE to use for these results - size_t totalTuples = outputResultStore.getNumberOfTuples(); - std::vector tempResultVector(totalTuples, falseValue); + size_t totalTuples = outputResultVector.getNumberOfTuples(); + auto tempResultStorePtr = DataStoreUtilities::CreateDataStore({totalTuples}, {1}, IDataAction::Mode::Execute); + AbstractDataStore& tempResultStore = *tempResultStorePtr.get(); + std::fill(tempResultStore.begin(), tempResultStore.end(), falseValue); nx::core::ArrayThreshold::ComparisonType compOperator = comparisonValue.getComparisonType(); nx::core::ArrayThreshold::ComparisonValue compValue = comparisonValue.getComparisonValue(); @@ -125,49 +154,23 @@ void ThresholdValue(const ArrayThreshold& comparisonValue, const DataStructure& usize componentIndex = comparisonValue.getComponentIndex(); - ThresholdFilterHelper helper(compOperator, compValue, componentIndex, tempResultVector); + ThresholdFilterHelper helper(compOperator, compValue, componentIndex, tempResultStore); const auto& iDataArray = dataStructure.getDataRefAs(inputDataArrayPath); ExecuteDataFunction(ExecuteThresholdHelper{}, iDataArray.getDataType(), helper, iDataArray, trueValue, falseValue); - if(replaceInput) - { - if(inverse) - { - std::reverse(tempResultVector.begin(), tempResultVector.end()); - } - // copy the temp uint8 vector to the final uint8 result array - for(size_t i = 0; i < totalTuples; i++) - { - outputResultStore[i] = tempResultVector[i]; - } - } - else - { - // insert into current threshold - InsertThreshold(totalTuples, outputResultStore, unionOperator, tempResultVector, inverse, trueValue, falseValue); - } + ApplyThresholdValues(comparisonValue, outputResultVector, tempResultStore, replaceInput, trueValue, falseValue); } -struct ThresholdValueFunctor -{ - template - void operator()(const ArrayThreshold& comparisonValue, const DataStructure& dataStructure, IDataArray& outputResultArray, int32_t& err, bool replaceInput, bool inverse, T trueValue, T falseValue) - { - // Traditionally we would do a check to ensure we get a valid pointer, I'm forgoing that check because it - // was essentially done in the preflight part. - ThresholdValue(comparisonValue, dataStructure, outputResultArray.template getIDataStoreRefAs>(), err, replaceInput, inverse, trueValue, falseValue); - } -}; - template -void ThresholdSet(const ArrayThresholdSet& inputComparisonSet, const DataStructure& dataStructure, AbstractDataStore& outputResultStore, int32_t& err, bool replaceInput, bool inverse, T trueValue, - T falseValue) +void ThresholdSet(const ArrayThresholdSet& inputComparisonSet, const DataStructure& dataStructure, AbstractDataStore& outputResultVector, int32_t& err, bool replaceInput, T trueValue, T falseValue) { // Get the total number of tuples, create and initialize an array with FALSE to use for these results - size_t totalTuples = outputResultStore.getNumberOfTuples(); - std::vector tempResultVector(totalTuples, falseValue); + size_t totalTuples = outputResultVector.getNumberOfTuples(); + auto tempResultStorePtr = DataStoreUtilities::CreateDataStore({totalTuples}, {1}, IDataAction::Mode::Execute); + AbstractDataStore& tempResultStore = *tempResultStorePtr.get(); + std::fill(tempResultStore.begin(), tempResultStore.end(), falseValue); bool firstValueFound = false; @@ -177,44 +180,38 @@ void ThresholdSet(const ArrayThresholdSet& inputComparisonSet, const DataStructu const IArrayThreshold* thresholdPtr = threshold.get(); if(const auto* comparisonSet = dynamic_cast(thresholdPtr); comparisonSet != nullptr) { - ThresholdSet(*comparisonSet, dataStructure, outputResultStore, err, !firstValueFound, false, trueValue, falseValue); + ThresholdSet(*comparisonSet, dataStructure, tempResultStore, err, !firstValueFound, trueValue, falseValue); firstValueFound = true; } else if(const auto* comparisonValue = dynamic_cast(thresholdPtr); comparisonValue != nullptr) { - ThresholdValue(*comparisonValue, dataStructure, outputResultStore, err, !firstValueFound, false, trueValue, falseValue); + ThresholdValue(*comparisonValue, dataStructure, tempResultStore, err, !firstValueFound, trueValue, falseValue); firstValueFound = true; } } - if(replaceInput) - { - if(inverse) - { - std::reverse(tempResultVector.begin(), tempResultVector.end()); - } - // copy the temp uint8 vector to the final uint8 result array - for(size_t i = 0; i < totalTuples; i++) - { - outputResultStore[i] = tempResultVector[i]; - } - } - else - { - // insert into current threshold - InsertThreshold(totalTuples, outputResultStore, inputComparisonSet.getUnionOperator(), tempResultVector, inverse, trueValue, falseValue); - } + // Apply resulting values to output + ApplyThresholdValues(inputComparisonSet, outputResultVector, tempResultStore, replaceInput, trueValue, falseValue); } struct ThresholdSetFunctor { template - void operator()(const ArrayThresholdSet& inputComparisonSet, const DataStructure& dataStructure, IDataArray& outputResultArray, int32_t& err, bool replaceInput, bool inverse, T trueValue, - T falseValue) + void operator()(const ArrayThresholdSet& inputComparisonSet, const DataStructure& dataStructure, IDataArray& outputResultArray, int32_t& err, bool replaceInput, T trueValue, T falseValue) { // Traditionally we would do a check to ensure we get a valid pointer, I'm forgoing that check because it // was essentially done in the preflight part. - ThresholdSet(inputComparisonSet, dataStructure, outputResultArray.template getIDataStoreRefAs>(), err, replaceInput, inverse, trueValue, falseValue); + auto& outputDataStore = outputResultArray.template getIDataStoreRefAs>(); + usize totalTuples = outputDataStore.getNumberOfTuples(); + auto tempResultStorePtr = DataStoreUtilities::CreateDataStore({totalTuples}, {1}, IDataAction::Mode::Execute); + AbstractDataStore& tempResultStore = *tempResultStorePtr.get(); + std::fill(tempResultStore.begin(), tempResultStore.end(), falseValue); + ThresholdSet(inputComparisonSet, dataStructure, tempResultStore, err, replaceInput, trueValue, falseValue); + + for(size_t i = 0; i < totalTuples; i++) + { + outputDataStore[i] = tempResultStore[i]; + } } }; } // namespace @@ -250,26 +247,8 @@ Result<> MultiThresholdObjects::operator()() DataPath maskArrayPath = (*thresholdsObject.getRequiredPaths().begin()).replaceName(maskArrayName); int32_t err = 0; ArrayThresholdSet::CollectionType thresholdSet = thresholdsObject.getArrayThresholds(); - for(const std::shared_ptr& threshold : thresholdSet) - { - if(m_ShouldCancel) - { - return {}; - } - const IArrayThreshold* thresholdPtr = threshold.get(); - if(const auto* comparisonSet = dynamic_cast(thresholdPtr); comparisonSet != nullptr) - { - ExecuteDataFunction(ThresholdSetFunctor{}, maskArrayType, *comparisonSet, m_DataStructure, m_DataStructure.getDataRefAs(maskArrayPath), err, !firstValueFound, - thresholdsObject.isInverted(), trueValue, falseValue); - firstValueFound = true; - } - else if(const auto* comparisonValue = dynamic_cast(thresholdPtr); comparisonValue != nullptr) - { - ExecuteDataFunction(ThresholdValueFunctor{}, maskArrayType, *comparisonValue, m_DataStructure, m_DataStructure.getDataRefAs(maskArrayPath), err, !firstValueFound, - thresholdsObject.isInverted(), trueValue, falseValue); - firstValueFound = true; - } - } + + ExecuteDataFunction(ThresholdSetFunctor{}, maskArrayType, thresholdsObject, m_DataStructure, m_DataStructure.getDataRefAs(maskArrayPath), err, !firstValueFound, trueValue, falseValue); return {}; } diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/MultiThresholdObjectsFilter.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/MultiThresholdObjectsFilter.cpp index 85c1535dc6..34dc6bff18 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/MultiThresholdObjectsFilter.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/MultiThresholdObjectsFilter.cpp @@ -164,7 +164,6 @@ IFilter::PreflightResult MultiThresholdObjectsFilter::preflightImpl(const DataSt // Check for same number of tuples and components usize numTuples = dataArray.getNumberOfTuples(); - usize numComponents = dataArray.getNumberOfComponents(); for(const auto& dataPath : thresholdPaths) { const auto& currentDataArray = dataStructure.getDataRefAs(dataPath); @@ -174,13 +173,6 @@ IFilter::PreflightResult MultiThresholdObjectsFilter::preflightImpl(const DataSt auto errorMessage = fmt::format("Data Arrays do not have same equal number of tuples. '{}:{}' and '{}:{}'", firstDataPath.toString(), numTuples, dataPath.toString(), currentNumTuples); return MakePreflightErrorResult(to_underlying(ErrorCodes::UnequalTuples), errorMessage); } - usize currentNumComponents = currentDataArray.getNumberOfComponents(); - if(currentNumComponents != numComponents) - { - auto errorMessage = - fmt::format("Data Arrays do not have same equal number of components. '{}:{}' and '{}:{}'", firstDataPath.toString(), numComponents, dataPath.toString(), currentNumComponents); - return MakePreflightErrorResult(to_underlying(ErrorCodes::UnequalComponents), errorMessage); - } } Result<> componentIndicesResult = CheckComponentIndicesInThresholds(thresholdsObject, dataStructure); @@ -223,8 +215,8 @@ IFilter::PreflightResult MultiThresholdObjectsFilter::preflightImpl(const DataSt } // Create the output boolean array - auto action = - std::make_unique(maskArrayType, dataArray.getIDataStoreRef().getTupleShape(), std::vector{1}, firstDataPath.replaceName(maskArrayName), dataArray.getDataFormat()); + const auto& dataStore = dataArray.getIDataStoreRef(); + auto action = std::make_unique(maskArrayType, dataStore.getTupleShape(), std::vector{1}, firstDataPath.replaceName(maskArrayName), dataArray.getDataFormat()); OutputActions actions; actions.appendAction(std::move(action)); diff --git a/src/Plugins/SimplnxCore/test/MultiThresholdObjectsTest.cpp b/src/Plugins/SimplnxCore/test/MultiThresholdObjectsTest.cpp index da9ded6ad8..cb0fd4dc1b 100644 --- a/src/Plugins/SimplnxCore/test/MultiThresholdObjectsTest.cpp +++ b/src/Plugins/SimplnxCore/test/MultiThresholdObjectsTest.cpp @@ -31,20 +31,39 @@ const DataPath k_ThresholdArrayPath = k_ImageCellDataName.createChildPath(k_Thre const DataPath k_MismatchingComponentsArrayPath = k_ImageCellDataName.createChildPath("MismatchingComponentsArray"); const DataPath k_MismatchingTuplesArrayPath({"MismatchingTuplesArray"}); +constexpr int8 k_TupleCount = 5; +constexpr int8 k_MultiComponentCount = 3; + +constexpr float64 k_FloatValueIncrement = 0.01; + +constexpr int32 InputIntValue(int32 index) +{ + return index; +} + +constexpr int32 InputIntComponentValue(int32 tuple, int32 component) +{ + return (tuple + component) % 2 == 0 ? -tuple : tuple; +} + +constexpr float64 InputFloatValue(int32 index) +{ + return (index + 1) * k_FloatValueIncrement; +} + DataStructure CreateTestDataStructure() { DataStructure dataStructure; // Create two test arrays, a float array and a int array // Set up geometry for tuples, a cuboid with dimensions 20, 10, 1 ImageGeom* image = ImageGeom::Create(dataStructure, k_ImageGeometry); - std::vector dims = {20, 1, 1}; + std::vector dims = {k_TupleCount, 1, 1}; image->setDimensions(dims); - ShapeType tDims = {20}; + ShapeType tDims = {k_TupleCount}; ShapeType cDims = {1}; - ShapeType cDimsMulti = {3}; - float fnum = 0.0f; - int inum = 0; + ShapeType cDimsMulti = {k_MultiComponentCount}; + AttributeMatrix* am = AttributeMatrix::Create(dataStructure, k_CellData, tDims, image->getId()); Float32Array* data = Float32Array::CreateWithStore(dataStructure, k_TestArrayFloatName, tDims, cDims, am->getId()); Int32Array* data1 = Int32Array::CreateWithStore(dataStructure, k_TestArrayIntName, tDims, cDims, am->getId()); @@ -56,25 +75,212 @@ DataStructure CreateTestDataStructure() invalid2->fill(2.0); usize numComponents = multiComponentData->getNumberOfComponents(); - int32 sign = 1; - - // Fill the float array with {.01,.02,.03,.04,.05,.06,.07,.08,.09,.10,.11,.12,.13,.14,.15.,16,.17,.18,.19,.20} - // Fill the int array with { 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19 } - // Fill multi-component array with {{0, 0, 0}, {1, -1, 1}, {-2, 2, -2}, ..., {17, -17, 17}, {-18, 18, -18}, {19, -19, 19}} - for(usize i = 0; i < 20; i++) - { - fnum += 0.01f; - (*data)[i] = fnum; // float array - (*data1)[i] = inum; // int array - multiComponentData->setComponent(i, 0, i * -sign); - multiComponentData->setComponent(i, 1, i * sign); - multiComponentData->setComponent(i, 2, i * -sign); - sign *= -1; - ++inum; + + // Fill the float array with {.01,.02,.03,.04,.05} + // Fill the int array with { 0,1,2,3,4} + // Fill multi-component array with {{0, 0, 0}, {1, -1, 1}, {-2, 2, -2}, {3, -3, 3}, {-4, 4, -4}} + for(usize i = 0; i < k_TupleCount; i++) + { + (*data)[i] = InputFloatValue(i); // float array + (*data1)[i] = InputIntValue(i); // int array + + for(usize j = 0; j < k_MultiComponentCount; j++) + { + multiComponentData->setComponent(i, j, InputIntComponentValue(i, j)); + } } return dataStructure; } +/** + * @brief Creates a single threshold for the filter to use. + * @param arrayPath Input DataArray path + * @param comparisonType type of comparison + * @param value Value to threshold against + * @param isInverted Should the threshold output be inverted + * componentIndex Component index of the array to threshold against. + */ +ArrayThresholdSet CreateSingleThreshold(const DataPath& arrayPath, ArrayThreshold::ComparisonType comparisonType, double value, bool isInverted, int componentIndex) +{ + ArrayThresholdSet thresholdSet; + auto threshold = std::make_shared(); + threshold->setArrayPath(arrayPath); + threshold->setComparisonType(comparisonType); + threshold->setComparisonValue(value); + threshold->setComponentIndex(componentIndex); + threshold->setInverted(isInverted); + thresholdSet.setArrayThresholds({threshold}); + + return thresholdSet; +} + +/** + * @brief Runs the MultiThresholdObjects filter on the provided threshold set + * @param dataStructure + * @param thresholdSet ThresholdSet to use for the MultiThresholdObjectsFilter + */ +void RunThresholdSetTest(DataStructure& dataStructure, ArrayThresholdSet thresholdSet) +{ + MultiThresholdObjectsFilter filter; + Arguments args; + + args.insertOrAssign(MultiThresholdObjectsFilter::k_ArrayThresholdsObject_Key, std::make_any(thresholdSet)); + args.insertOrAssign(MultiThresholdObjectsFilter::k_CreatedDataName_Key, std::make_any(k_ThresholdArrayName)); + args.insertOrAssign(MultiThresholdObjectsFilter::k_CreatedMaskType_Key, std::make_any(DataType::boolean)); + + // Preflight the filter and check result + auto preflightResult = filter.preflight(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions) + + // Execute the filter and check the result + auto executeResult = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result) + + // Require that the mask array only has one component + const auto* thresholdArrayPtr = dataStructure.getDataAs(k_ThresholdArrayPath); + REQUIRE(thresholdArrayPtr != nullptr); + + REQUIRE(thresholdArrayPtr->getNumberOfComponents() == 1); +} + +/** + * @brief Runs the MultiThresholdObjects filter on the provided DataStructure using a single array threshold. + * @param dataStructure + * @param arrayPath Path to use for the threshold DataArray + * @param comparisonType Type of comparison to perform + * @param value Value to threshold against + * @param isInverted should the output mask value be inverted + * @param componentIndex Which component of the array the threshold should use. + */ +void RunSingleThresholdTest(DataStructure& dataStructure, const DataPath& arrayPath, ArrayThreshold::ComparisonType comparisonType, double value, bool isInverted, int32 componentIndex = 0) +{ + auto thresholdSet = CreateSingleThreshold(arrayPath, comparisonType, value, isInverted, componentIndex); + RunThresholdSetTest(dataStructure, thresholdSet); +} + +// Integer checks +bool ExpectedIntSingleComponentMask(ArrayThreshold::ComparisonType comparisonType, int32 i, double thresholdValue, bool isInverted) +{ + bool expected = false; + + switch(comparisonType) + { + case ArrayThreshold::ComparisonType::GreaterThan: + expected = InputIntValue(i) > thresholdValue; + break; + case ArrayThreshold::ComparisonType::LessThan: + expected = InputIntValue(i) < thresholdValue; + break; + case ArrayThreshold::ComparisonType::Operator_Equal: + expected = InputIntValue(i) == thresholdValue; + break; + case ArrayThreshold::ComparisonType::Operator_NotEqual: + expected = InputIntValue(i) != thresholdValue; + break; + } + + if(isInverted) + { + expected = !expected; + } + return expected; +} + +void CheckIntTestDataSingleComponent(const DataStructure& dataStructure, ArrayThreshold::ComparisonType comparisonType, double thresholdValue, bool isInverted) +{ + const auto* thresholdArrayPtr = dataStructure.getDataAs(k_ThresholdArrayPath); + REQUIRE(thresholdArrayPtr != nullptr); + + auto& thresholdStore = thresholdArrayPtr->getDataStoreRef(); + + for(usize i = 0; i < k_TupleCount; i++) + { + REQUIRE(thresholdStore[i] == ExpectedIntSingleComponentMask(comparisonType, i, thresholdValue, isInverted)); + } +} + +// Floating point checks +bool ExpectedFloatSingleComponentMask(ArrayThreshold::ComparisonType comparisonType, int32 i, double thresholdValue, bool isInverted) +{ + bool expected = false; + + switch(comparisonType) + { + case ArrayThreshold::ComparisonType::GreaterThan: + expected = InputFloatValue(i) > thresholdValue; + break; + case ArrayThreshold::ComparisonType::LessThan: + expected = InputFloatValue(i) < thresholdValue; + break; + case ArrayThreshold::ComparisonType::Operator_Equal: + expected = InputFloatValue(i) == thresholdValue; + break; + case ArrayThreshold::ComparisonType::Operator_NotEqual: + expected = InputFloatValue(i) != thresholdValue; + break; + } + + if(isInverted) + { + expected = !expected; + } + return expected; +} + +void CheckFloatTestDataSingleComponent(const DataStructure& dataStructure, ArrayThreshold::ComparisonType comparisonType, double thresholdValue, bool isInverted) +{ + const auto* thresholdArrayPtr = dataStructure.getDataAs(k_ThresholdArrayPath); + REQUIRE(thresholdArrayPtr != nullptr); + + auto& thresholdStore = thresholdArrayPtr->getDataStoreRef(); + + for(usize i = 0; i < k_TupleCount; i++) + { + REQUIRE(thresholdStore[i] == ExpectedFloatSingleComponentMask(comparisonType, i, thresholdValue, isInverted)); + } +} + +// Multi-component checks +bool ExpectedIntMultiComponentMask(ArrayThreshold::ComparisonType comparisonType, int32 i, double thresholdValue, bool isInverted, int32 componentIndex) +{ + bool expected = false; + + switch(comparisonType) + { + case ArrayThreshold::ComparisonType::GreaterThan: + expected = InputIntComponentValue(i, componentIndex) > thresholdValue; + break; + case ArrayThreshold::ComparisonType::LessThan: + expected = InputIntComponentValue(i, componentIndex) < thresholdValue; + break; + case ArrayThreshold::ComparisonType::Operator_Equal: + expected = InputIntComponentValue(i, componentIndex) == thresholdValue; + break; + case ArrayThreshold::ComparisonType::Operator_NotEqual: + expected = InputIntComponentValue(i, componentIndex) != thresholdValue; + break; + } + + if(isInverted) + { + expected = !expected; + } + return expected; +} + +void CheckIntTestDataMultiComponent(const DataStructure& dataStructure, ArrayThreshold::ComparisonType comparisonType, double thresholdValue, bool isInverted, int32 componentIndex) +{ + const auto* thresholdArrayPtr = dataStructure.getDataAs(k_ThresholdArrayPath); + REQUIRE(thresholdArrayPtr != nullptr); + + auto& thresholdStore = thresholdArrayPtr->getDataStoreRef(); + + for(usize i = 0; i < k_TupleCount; i++) + { + REQUIRE(thresholdStore[i] == ExpectedIntMultiComponentMask(comparisonType, i, thresholdValue, isInverted, componentIndex)); + } +} + template float64 GetOutOfBoundsMinimumValue() { @@ -97,149 +303,378 @@ float64 GetOutOfBoundsMaximumValue() } } // namespace -TEST_CASE("SimplnxCore::MultiThresholdObjects: Valid Execution", "[SimplnxCore][MultiThresholdObjectsFilter]") +TEST_CASE("SimplnxCore::MultiThresholdObjects: Valid Single Thresholds: Int", "[SimplnxCore][MultiThresholdObjectsFilter]") { UnitTest::LoadPlugins(); DataStructure dataStructure = CreateTestDataStructure(); + const DataPath targetArray = k_TestArrayIntPath; + double thresholdValue = GENERATE(-1.0, 0.0, 1.0, 2.0, 3.0, 4.0, 22.0, 5.5); + bool isInverted = GENERATE(false, true); - SECTION("Float Array Threshold") + SECTION("ArrayThreshold: >") { - MultiThresholdObjectsFilter filter; - Arguments args; + RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::GreaterThan, thresholdValue, isInverted); + CheckIntTestDataSingleComponent(dataStructure, ArrayThreshold::ComparisonType::GreaterThan, thresholdValue, isInverted); + } - ArrayThresholdSet thresholdSet; - auto threshold = std::make_shared(); - threshold->setArrayPath(k_TestArrayFloatPath); - threshold->setComparisonType(ArrayThreshold::ComparisonType::GreaterThan); - threshold->setComparisonValue(0.1); - thresholdSet.setArrayThresholds({threshold}); + SECTION("ArrayThreshold: <") + { + RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::LessThan, thresholdValue, isInverted); + CheckIntTestDataSingleComponent(dataStructure, ArrayThreshold::ComparisonType::LessThan, thresholdValue, isInverted); + } - args.insertOrAssign(MultiThresholdObjectsFilter::k_ArrayThresholdsObject_Key, std::make_any(thresholdSet)); - args.insertOrAssign(MultiThresholdObjectsFilter::k_CreatedDataName_Key, std::make_any(k_ThresholdArrayName)); - args.insertOrAssign(MultiThresholdObjectsFilter::k_CreatedMaskType_Key, std::make_any(DataType::boolean)); + SECTION("ArrayThreshold: ==") + { + RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::Operator_Equal, thresholdValue, isInverted); + CheckIntTestDataSingleComponent(dataStructure, ArrayThreshold::ComparisonType::Operator_Equal, thresholdValue, isInverted); + CheckIntTestDataSingleComponent(dataStructure, ArrayThreshold::ComparisonType::Operator_NotEqual, thresholdValue, !isInverted); + } + SECTION("ArrayThreshold: !=") + { + RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::Operator_NotEqual, thresholdValue, isInverted); + CheckIntTestDataSingleComponent(dataStructure, ArrayThreshold::ComparisonType::Operator_Equal, thresholdValue, !isInverted); + CheckIntTestDataSingleComponent(dataStructure, ArrayThreshold::ComparisonType::Operator_NotEqual, thresholdValue, isInverted); + } +} - // Preflight the filter and check result - auto preflightResult = filter.preflight(dataStructure, args); - SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions) +TEST_CASE("SimplnxCore::MultiThresholdObjects: Valid Single Thresholds: Float", "[SimplnxCore][MultiThresholdObjectsFilter]") +{ + UnitTest::LoadPlugins(); - // Execute the filter and check the result - auto executeResult = filter.execute(dataStructure, args); - SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result) + DataStructure dataStructure = CreateTestDataStructure(); + const DataPath targetArray = k_TestArrayFloatPath; + double thresholdValue = GENERATE(0.0, 0.01, 0.02, 0.03, 0.04, 26.2); + bool isInverted = GENERATE(false, true); - auto* thresholdArray = dataStructure.getDataAs(k_ThresholdArrayPath); - REQUIRE(thresholdArray != nullptr); + SECTION("ArrayThreshold: >") + { + RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::GreaterThan, thresholdValue, isInverted); + CheckFloatTestDataSingleComponent(dataStructure, ArrayThreshold::ComparisonType::GreaterThan, thresholdValue, isInverted); + } - // For the comparison value of 0.1, the threshold array elements 0 to 9 should be false and 10 through 19 should be true - for(usize i = 0; i < 20; i++) - { - if(i < 10) - { - REQUIRE((*thresholdArray)[i] == false); - } - else - { - REQUIRE((*thresholdArray)[i] == true); - } - } + SECTION("ArrayThreshold: <") + { + RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::LessThan, thresholdValue, isInverted); + CheckFloatTestDataSingleComponent(dataStructure, ArrayThreshold::ComparisonType::LessThan, thresholdValue, isInverted); } - SECTION("Int Array Threshold") + SECTION("ArrayThreshold: ==") + { + RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::Operator_Equal, thresholdValue, isInverted); + CheckFloatTestDataSingleComponent(dataStructure, ArrayThreshold::ComparisonType::Operator_Equal, thresholdValue, isInverted); + CheckFloatTestDataSingleComponent(dataStructure, ArrayThreshold::ComparisonType::Operator_NotEqual, thresholdValue, !isInverted); + } + SECTION("ArrayThreshold: !=") { - MultiThresholdObjectsFilter filter; - Arguments args; + RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::Operator_NotEqual, thresholdValue, isInverted); + CheckFloatTestDataSingleComponent(dataStructure, ArrayThreshold::ComparisonType::Operator_Equal, thresholdValue, !isInverted); + CheckFloatTestDataSingleComponent(dataStructure, ArrayThreshold::ComparisonType::Operator_NotEqual, thresholdValue, isInverted); + } +} - ArrayThresholdSet thresholdSet; - auto threshold = std::make_shared(); - threshold->setArrayPath(k_TestArrayIntPath); - threshold->setComparisonType(ArrayThreshold::ComparisonType::GreaterThan); - threshold->setComparisonValue(15); - thresholdSet.setArrayThresholds({threshold}); +TEST_CASE("SimplnxCore::MultiThresholdObjects: Valid Single Thresholds: Int Multi-Component", "[SimplnxCore][MultiThresholdObjectsFilter]") +{ + UnitTest::LoadPlugins(); - args.insertOrAssign(MultiThresholdObjectsFilter::k_ArrayThresholdsObject_Key, std::make_any(thresholdSet)); - args.insertOrAssign(MultiThresholdObjectsFilter::k_CreatedDataName_Key, std::make_any(k_ThresholdArrayName)); - args.insertOrAssign(MultiThresholdObjectsFilter::k_CreatedMaskType_Key, std::make_any(DataType::boolean)); + DataStructure dataStructure = CreateTestDataStructure(); + const DataPath targetArray = k_MultiComponentArrayPath; + double thresholdValue = GENERATE(-1.0, 0.0, 1.0, 2.0, 3.0, 4.0, 22.0, 5.5); + bool isInverted = GENERATE(false, true); + int32 componentIndex = GENERATE(0, 1, 2); - // Preflight the filter and check result - auto preflightResult = filter.preflight(dataStructure, args); - SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions) + SECTION("ArrayThreshold: >") + { + RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::GreaterThan, thresholdValue, isInverted, componentIndex); + CheckIntTestDataMultiComponent(dataStructure, ArrayThreshold::ComparisonType::GreaterThan, thresholdValue, isInverted, componentIndex); + } - // Execute the filter and check the result - auto executeResult = filter.execute(dataStructure, args); - SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result) + SECTION("ArrayThreshold: <") + { + RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::LessThan, thresholdValue, isInverted, componentIndex); + CheckIntTestDataMultiComponent(dataStructure, ArrayThreshold::ComparisonType::LessThan, thresholdValue, isInverted, componentIndex); + } - auto* thresholdArray = dataStructure.getDataAs(k_ThresholdArrayPath); - REQUIRE(thresholdArray != nullptr); + SECTION("ArrayThreshold: ==") + { + RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::Operator_Equal, thresholdValue, isInverted, componentIndex); + CheckIntTestDataMultiComponent(dataStructure, ArrayThreshold::ComparisonType::Operator_Equal, thresholdValue, isInverted, componentIndex); + CheckIntTestDataMultiComponent(dataStructure, ArrayThreshold::ComparisonType::Operator_NotEqual, thresholdValue, !isInverted, componentIndex); + } + SECTION("ArrayThreshold: !=") + { + RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::Operator_NotEqual, thresholdValue, isInverted, componentIndex); + CheckIntTestDataMultiComponent(dataStructure, ArrayThreshold::ComparisonType::Operator_Equal, thresholdValue, !isInverted, componentIndex); + CheckIntTestDataMultiComponent(dataStructure, ArrayThreshold::ComparisonType::Operator_NotEqual, thresholdValue, isInverted, componentIndex); + } +} - // For the comparison value of 0.1, the threshold array elements 0 to 9 should be false and 10 through 19 should be true - for(usize i = 0; i < 20; i++) - { - if(i <= 15) - { - REQUIRE((*thresholdArray)[i] == false); - } - else - { - REQUIRE((*thresholdArray)[i] == true); - } - } +/** + * @brief Creates a single threshold for the filter to use. + * @param arrayPath Input DataArray path + * @param comparisonType type of comparison + * @param value Value to threshold against + * @param isInverted Should the threshold output be inverted + * componentIndex Component index of the array to threshold against. + * unionOperator Union operator to apply on the threshold. Defaults to And + */ +std::shared_ptr CreateArrayThreshold(const DataPath& arrayPath, ArrayThreshold::ComparisonType comparisonType, double value, bool isInverted, int componentIndex, + ArrayThreshold::UnionOperator unionOperator = ArrayThreshold::UnionOperator::And) +{ + auto threshold = std::make_shared(); + threshold->setArrayPath(arrayPath); + threshold->setComparisonType(comparisonType); + threshold->setComparisonValue(value); + threshold->setComponentIndex(componentIndex); + threshold->setInverted(isInverted); + threshold->setUnionOperator(unionOperator); + + return threshold; +} + +ArrayThresholdSet CreateThresholdSet1() +{ + ArrayThresholdSet thresholdSet; + + // Threshold: Int > 2 + auto threshold1 = CreateArrayThreshold(k_TestArrayIntPath, ArrayThreshold::ComparisonType::GreaterThan, 2.0, false, 0, ArrayThreshold::UnionOperator::And); + // Threshold: Float < 0.025 + auto threshold2 = CreateArrayThreshold(k_TestArrayFloatPath, ArrayThreshold::ComparisonType::LessThan, 0.025, false, 0, ArrayThreshold::UnionOperator::And); + // Threshold: Int[1] > 0.0 : inverted + auto threshold3 = CreateArrayThreshold(k_MultiComponentArrayPath, ArrayThreshold::ComparisonType::GreaterThan, 0.0, true, 1, ArrayThreshold::UnionOperator::And); + + thresholdSet.setArrayThresholds({threshold1, threshold2, threshold3}); + + return thresholdSet; +} + +bool ExpectedThresholdSet1Mask(usize index, bool inverted) +{ + bool expectedThreshold1 = ExpectedIntSingleComponentMask(ArrayThreshold::ComparisonType::GreaterThan, index, 2.0, false); + bool expectedThreshold2 = ExpectedFloatSingleComponentMask(ArrayThreshold::ComparisonType::LessThan, index, 0.025, false); + bool expectedThreshold3 = ExpectedIntMultiComponentMask(ArrayThreshold::ComparisonType::GreaterThan, index, 0.0, true, 1); + + bool expected = expectedThreshold1 && expectedThreshold2 && expectedThreshold3; + if(inverted) + { + expected = !expected; } + return expected; +} - UnitTest::CheckArraysInheritTupleDims(dataStructure); +ArrayThresholdSet CreateThresholdSet2() +{ + ArrayThresholdSet thresholdSet; + + // Threshold: Int == 1 + auto threshold1 = CreateArrayThreshold(k_TestArrayIntPath, ArrayThreshold::ComparisonType::Operator_Equal, 1.0, false, 0, ArrayThreshold::UnionOperator::And); + // Threshold: Float != 5.0 + auto threshold2 = CreateArrayThreshold(k_TestArrayFloatPath, ArrayThreshold::ComparisonType::Operator_NotEqual, 5.0, false, 0, ArrayThreshold::UnionOperator::Or); + // Threshold: Int[0] < 0.0 : inverted + auto threshold3 = CreateArrayThreshold(k_MultiComponentArrayPath, ArrayThreshold::ComparisonType::LessThan, 0.0, true, 0, ArrayThreshold::UnionOperator::And); + + thresholdSet.setArrayThresholds({threshold1, threshold2, threshold3}); + + return thresholdSet; } -TEMPLATE_TEST_CASE("SimplnxCore::MultiThresholdObjects: Valid Execution - Custom Values", "[SimplnxCore][MultiThresholdObjectsFilter]", int8, uint8, int16, uint16, int32, uint32, int64, uint64, - float32, float64) +bool ExpectedThresholdSet2Mask(usize index, bool inverted) { - UnitTest::LoadPlugins(); + bool expectedThreshold1 = ExpectedIntSingleComponentMask(ArrayThreshold::ComparisonType::Operator_Equal, index, 1.0, false); + bool expectedThreshold2 = ExpectedFloatSingleComponentMask(ArrayThreshold::ComparisonType::Operator_NotEqual, index, 5.0, false); + bool expectedThreshold3 = ExpectedIntMultiComponentMask(ArrayThreshold::ComparisonType::LessThan, index, 0.0, true, 0); - MultiThresholdObjectsFilter filter; - DataStructure dataStructure = CreateTestDataStructure(); - Arguments args; + bool expected = (expectedThreshold1 || expectedThreshold2) && expectedThreshold3; + if(inverted) + { + expected = !expected; + } + return expected; +} - float64 trueValue = 25; - float64 falseValue = 10; +ArrayThresholdSet CreateThresholdSet3() +{ + ArrayThresholdSet thresholdSet; + auto set1 = std::make_shared(CreateThresholdSet1()); + auto set2 = std::make_shared(CreateThresholdSet2()); + + thresholdSet.setArrayThresholds({set1, set2}); + + return thresholdSet; +} + +ArrayThresholdSet CreateThresholdSet4() +{ ArrayThresholdSet thresholdSet; - auto threshold = std::make_shared(); - threshold->setArrayPath(k_TestArrayIntPath); - threshold->setComparisonType(ArrayThreshold::ComparisonType::GreaterThan); - threshold->setComparisonValue(15); - thresholdSet.setArrayThresholds({threshold}); - args.insertOrAssign(MultiThresholdObjectsFilter::k_ArrayThresholdsObject_Key, std::make_any(thresholdSet)); - args.insertOrAssign(MultiThresholdObjectsFilter::k_CreatedDataName_Key, std::make_any(k_ThresholdArrayName)); - args.insertOrAssign(MultiThresholdObjectsFilter::k_UseCustomTrueValue, std::make_any(true)); - args.insertOrAssign(MultiThresholdObjectsFilter::k_CustomTrueValue, std::make_any(trueValue)); - args.insertOrAssign(MultiThresholdObjectsFilter::k_UseCustomFalseValue, std::make_any(true)); - args.insertOrAssign(MultiThresholdObjectsFilter::k_CustomFalseValue, std::make_any(falseValue)); - args.insertOrAssign(MultiThresholdObjectsFilter::k_CreatedMaskType_Key, std::make_any(GetDataType())); + auto set1 = std::make_shared(CreateThresholdSet1()); + auto set2 = std::make_shared(CreateThresholdSet2()); + set2->setUnionOperator(ArrayThreshold::UnionOperator::Or); - // Preflight the filter and check result - auto preflightResult = filter.preflight(dataStructure, args); - SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions) + thresholdSet.setArrayThresholds({set1, set2}); - // Execute the filter and check the result - auto executeResult = filter.execute(dataStructure, args); - SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result) + return thresholdSet; +} + +ArrayThresholdSet CreateThresholdSet5() +{ + ArrayThresholdSet thresholdSet; - auto* thresholdArray = dataStructure.getDataAs>(k_ThresholdArrayPath); - REQUIRE(thresholdArray != nullptr); + auto set1 = std::make_shared(CreateThresholdSet1()); + auto set2 = std::make_shared(CreateThresholdSet2()); + set2->setUnionOperator(ArrayThreshold::UnionOperator::Or); + set2->setInverted(true); - // For the comparison value of 0.1, the threshold array elements 0 to 9 should be false and 10 through 19 should be true - for(usize i = 0; i < 20; i++) + thresholdSet.setArrayThresholds({set1, set2}); + + return thresholdSet; +} + +void CheckThresholdSet1(DataStructure& dataStructure, bool inverted) +{ + const auto* thresholdArrayPtr = dataStructure.getDataAs(k_ThresholdArrayPath); + REQUIRE(thresholdArrayPtr != nullptr); + + auto& thresholdStore = thresholdArrayPtr->getDataStoreRef(); + + for(usize i = 0; i < k_TupleCount; i++) { - if(i <= 15) + REQUIRE(thresholdStore[i] == ExpectedThresholdSet1Mask(i, inverted)); + } +} + +void CheckThresholdSet2(DataStructure& dataStructure, bool inverted) +{ + const auto* thresholdArrayPtr = dataStructure.getDataAs(k_ThresholdArrayPath); + REQUIRE(thresholdArrayPtr != nullptr); + + auto& thresholdStore = thresholdArrayPtr->getDataStoreRef(); + + for(usize i = 0; i < k_TupleCount; i++) + { + bool value = thresholdStore[i]; + bool expected = ExpectedThresholdSet2Mask(i, inverted); + REQUIRE(thresholdStore[i] == ExpectedThresholdSet2Mask(i, inverted)); + } +} + +void CheckThresholdSet3(DataStructure& dataStructure, bool inverted) +{ + const auto* thresholdArrayPtr = dataStructure.getDataAs(k_ThresholdArrayPath); + REQUIRE(thresholdArrayPtr != nullptr); + + auto& thresholdStore = thresholdArrayPtr->getDataStoreRef(); + + for(usize i = 0; i < k_TupleCount; i++) + { + bool expectedMask1 = ExpectedThresholdSet1Mask(i, false); + bool expectedMask2 = ExpectedThresholdSet2Mask(i, false); + + bool expected = expectedMask1 && expectedMask2; + if(inverted) { - REQUIRE((*thresholdArray)[i] == falseValue); + expected = !expected; } - else + + REQUIRE(thresholdStore[i] == expected); + } +} + +void CheckThresholdSet4(DataStructure& dataStructure, bool inverted) +{ + const auto* thresholdArrayPtr = dataStructure.getDataAs(k_ThresholdArrayPath); + REQUIRE(thresholdArrayPtr != nullptr); + + auto& thresholdStore = thresholdArrayPtr->getDataStoreRef(); + + for(usize i = 0; i < k_TupleCount; i++) + { + bool expectedMask1 = ExpectedThresholdSet1Mask(i, false); + bool expectedMask2 = ExpectedThresholdSet2Mask(i, false); + + bool expected = expectedMask1 || expectedMask2; + if(inverted) { - REQUIRE((*thresholdArray)[i] == trueValue); + expected = !expected; } + + REQUIRE(thresholdStore[i] == expected); + } +} + +void CheckThresholdSet5(DataStructure& dataStructure, bool inverted) +{ + const auto* thresholdArrayPtr = dataStructure.getDataAs(k_ThresholdArrayPath); + REQUIRE(thresholdArrayPtr != nullptr); + + auto& thresholdStore = thresholdArrayPtr->getDataStoreRef(); + + for(usize i = 0; i < k_TupleCount; i++) + { + bool expectedMask1 = ExpectedThresholdSet1Mask(i, false); + bool expectedMask2 = ExpectedThresholdSet2Mask(i, true); + + bool expected = expectedMask1 || expectedMask2; + if(inverted) + { + expected = !expected; + } + + REQUIRE(thresholdStore[i] == expected); } } +TEST_CASE("SimplnxCore::MultiThresholdObjects: Valid Threshold Sets", "[SimplnxCore][MultiThresholdObjectsFilter]") +{ + UnitTest::LoadPlugins(); + + DataStructure dataStructure = CreateTestDataStructure(); + bool isInverted = GENERATE(false, true); + + SECTION("ArraySet 1") + { + auto thresholdSet = CreateThresholdSet1(); + thresholdSet.setInverted(isInverted); + RunThresholdSetTest(dataStructure, thresholdSet); + CheckThresholdSet1(dataStructure, isInverted); + } + + SECTION("ArraySet 2") + { + auto thresholdSet = CreateThresholdSet2(); + thresholdSet.setInverted(isInverted); + RunThresholdSetTest(dataStructure, thresholdSet); + CheckThresholdSet2(dataStructure, isInverted); + } + + SECTION("ArraySet 3") + { + auto thresholdSet = CreateThresholdSet3(); + thresholdSet.setInverted(isInverted); + RunThresholdSetTest(dataStructure, thresholdSet); + CheckThresholdSet3(dataStructure, isInverted); + } + + SECTION("ArraySet 4") + { + auto thresholdSet = CreateThresholdSet4(); + thresholdSet.setInverted(isInverted); + RunThresholdSetTest(dataStructure, thresholdSet); + CheckThresholdSet4(dataStructure, isInverted); + } + + SECTION("ArraySet 5") + { + auto thresholdSet = CreateThresholdSet5(); + thresholdSet.setInverted(isInverted); + RunThresholdSetTest(dataStructure, thresholdSet); + CheckThresholdSet5(dataStructure, isInverted); + } +} + +// Invalid executions + TEST_CASE("SimplnxCore::MultiThresholdObjects: Invalid Execution", "[SimplnxCore][MultiThresholdObjectsFilter]") { UnitTest::LoadPlugins(); @@ -265,21 +700,6 @@ TEST_CASE("SimplnxCore::MultiThresholdObjects: Invalid Execution", "[SimplnxCore args.insertOrAssign(MultiThresholdObjectsFilter::k_ArrayThresholdsObject_Key, std::make_any(thresholdSet)); } - SECTION("Mismatching Components in Threshold Arrays") - { - ArrayThresholdSet thresholdSet; - auto threshold1 = std::make_shared(); - threshold1->setArrayPath(k_TestArrayFloatPath); - threshold1->setComparisonType(ArrayThreshold::ComparisonType::GreaterThan); - threshold1->setComparisonValue(0.1); - auto threshold2 = std::make_shared(); - threshold2->setArrayPath(k_MismatchingComponentsArrayPath); - threshold2->setComparisonType(ArrayThreshold::ComparisonType::GreaterThan); - threshold2->setComparisonValue(0.1); - thresholdSet.setArrayThresholds({threshold1, threshold2}); - - args.insertOrAssign(MultiThresholdObjectsFilter::k_ArrayThresholdsObject_Key, std::make_any(thresholdSet)); - } SECTION("Out of Bounds Component Index") { ArrayThresholdSet thresholdSet; @@ -424,394 +844,136 @@ TEST_CASE("SimplnxCore::MultiThresholdObjects: Invalid Execution - Boolean Custo UnitTest::CheckArraysInheritTupleDims(dataStructure); } +// DataType checks + template void checkMaskValues(const DataStructure& dataStructure, const DataPath& thresholdArrayPath) { auto* thresholdArrayPtr = dataStructure.getDataAs>(thresholdArrayPath); REQUIRE(thresholdArrayPtr != nullptr); - auto& thresholdArray = (*thresholdArrayPtr); + auto& thresholdStore = thresholdArrayPtr->getDataStoreRef(); // For the comparison value of 0.1, the threshold array elements 0 to 9 should be false and 10 through 19 should be true - for(usize i = 0; i < 20; i++) + for(usize i = 0; i < k_TupleCount; i++) { - if(i < 10) + if(i < 5) { - REQUIRE(thresholdArray[i] == static_cast(0)); + REQUIRE(thresholdStore[i] == static_cast(0)); } else { - REQUIRE(thresholdArray[i] == static_cast(1)); + REQUIRE(thresholdStore[i] == static_cast(1)); } } } -TEST_CASE("SimplnxCore::MultiThresholdObjects: Valid Execution, DataType", "[SimplnxCore][MultiThresholdObjectsFilter]") +template +void runMaskTypeFilter(MultiThresholdObjectsFilter& filter, Arguments& args, DataStructure& dataStructure) +{ + // Preflight the filter and check result + auto preflightResult = filter.preflight(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions) + + // Execute the filter and check the result + auto executeResult = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result) + + checkMaskValues(dataStructure, k_ThresholdArrayPath); +} + +TEST_CASE("SimplnxCore::MultiThresholdObjects: Valid Execution, Mask DataType", "[SimplnxCore][MultiThresholdObjectsFilter]") { UnitTest::LoadPlugins(); DataStructure dataStructure = CreateTestDataStructure(); + // Shared filter setup + MultiThresholdObjectsFilter filter; + Arguments args; + + ArrayThresholdSet thresholdSet; + auto threshold = std::make_shared(); + threshold->setArrayPath(k_TestArrayFloatPath); + threshold->setComparisonType(ArrayThreshold::ComparisonType::GreaterThan); + threshold->setComparisonValue(0.05); + thresholdSet.setArrayThresholds({threshold}); + + args.insertOrAssign(MultiThresholdObjectsFilter::k_ArrayThresholdsObject_Key, std::make_any(thresholdSet)); + args.insertOrAssign(MultiThresholdObjectsFilter::k_CreatedDataName_Key, std::make_any(k_ThresholdArrayName)); + // Signed SECTION("Int8 Threshold") { - MultiThresholdObjectsFilter filter; - Arguments args; - - ArrayThresholdSet thresholdSet; - auto threshold = std::make_shared(); - threshold->setArrayPath(k_TestArrayFloatPath); - threshold->setComparisonType(ArrayThreshold::ComparisonType::GreaterThan); - threshold->setComparisonValue(0.1); - thresholdSet.setArrayThresholds({threshold}); - - args.insertOrAssign(MultiThresholdObjectsFilter::k_ArrayThresholdsObject_Key, std::make_any(thresholdSet)); - args.insertOrAssign(MultiThresholdObjectsFilter::k_CreatedDataName_Key, std::make_any(k_ThresholdArrayName)); args.insertOrAssign(MultiThresholdObjectsFilter::k_CreatedMaskType_Key, std::make_any(DataType::int8)); - // Preflight the filter and check result - auto preflightResult = filter.preflight(dataStructure, args); - SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions) - - // Execute the filter and check the result - auto executeResult = filter.execute(dataStructure, args); - SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result) - - checkMaskValues(dataStructure, k_ThresholdArrayPath); + runMaskTypeFilter(filter, args, dataStructure); } SECTION("Int16 Threshold") { - MultiThresholdObjectsFilter filter; - Arguments args; - - ArrayThresholdSet thresholdSet; - auto threshold = std::make_shared(); - threshold->setArrayPath(k_TestArrayFloatPath); - threshold->setComparisonType(ArrayThreshold::ComparisonType::GreaterThan); - threshold->setComparisonValue(0.1); - thresholdSet.setArrayThresholds({threshold}); - - args.insertOrAssign(MultiThresholdObjectsFilter::k_ArrayThresholdsObject_Key, std::make_any(thresholdSet)); - args.insertOrAssign(MultiThresholdObjectsFilter::k_CreatedDataName_Key, std::make_any(k_ThresholdArrayName)); args.insertOrAssign(MultiThresholdObjectsFilter::k_CreatedMaskType_Key, std::make_any(DataType::int16)); - // Preflight the filter and check result - auto preflightResult = filter.preflight(dataStructure, args); - SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions) - - // Execute the filter and check the result - auto executeResult = filter.execute(dataStructure, args); - SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result) - - checkMaskValues(dataStructure, k_ThresholdArrayPath); + runMaskTypeFilter(filter, args, dataStructure); } SECTION("Int32 Threshold") { - MultiThresholdObjectsFilter filter; - Arguments args; - - ArrayThresholdSet thresholdSet; - auto threshold = std::make_shared(); - threshold->setArrayPath(k_TestArrayFloatPath); - threshold->setComparisonType(ArrayThreshold::ComparisonType::GreaterThan); - threshold->setComparisonValue(0.1); - thresholdSet.setArrayThresholds({threshold}); - - args.insertOrAssign(MultiThresholdObjectsFilter::k_ArrayThresholdsObject_Key, std::make_any(thresholdSet)); - args.insertOrAssign(MultiThresholdObjectsFilter::k_CreatedDataName_Key, std::make_any(k_ThresholdArrayName)); args.insertOrAssign(MultiThresholdObjectsFilter::k_CreatedMaskType_Key, std::make_any(DataType::int32)); - // Preflight the filter and check result - auto preflightResult = filter.preflight(dataStructure, args); - SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions) - - // Execute the filter and check the result - auto executeResult = filter.execute(dataStructure, args); - SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result) - - checkMaskValues(dataStructure, k_ThresholdArrayPath); + runMaskTypeFilter(filter, args, dataStructure); } SECTION("Int64 Threshold") { - MultiThresholdObjectsFilter filter; - Arguments args; - - ArrayThresholdSet thresholdSet; - auto threshold = std::make_shared(); - threshold->setArrayPath(k_TestArrayFloatPath); - threshold->setComparisonType(ArrayThreshold::ComparisonType::GreaterThan); - threshold->setComparisonValue(0.1); - thresholdSet.setArrayThresholds({threshold}); - - args.insertOrAssign(MultiThresholdObjectsFilter::k_ArrayThresholdsObject_Key, std::make_any(thresholdSet)); - args.insertOrAssign(MultiThresholdObjectsFilter::k_CreatedDataName_Key, std::make_any(k_ThresholdArrayName)); args.insertOrAssign(MultiThresholdObjectsFilter::k_CreatedMaskType_Key, std::make_any(DataType::int64)); - // Preflight the filter and check result - auto preflightResult = filter.preflight(dataStructure, args); - SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions) - - // Execute the filter and check the result - auto executeResult = filter.execute(dataStructure, args); - SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result) - - checkMaskValues(dataStructure, k_ThresholdArrayPath); + runMaskTypeFilter(filter, args, dataStructure); } // Unsigned SECTION("UInt8 Threshold") { - MultiThresholdObjectsFilter filter; - Arguments args; - - ArrayThresholdSet thresholdSet; - auto threshold = std::make_shared(); - threshold->setArrayPath(k_TestArrayFloatPath); - threshold->setComparisonType(ArrayThreshold::ComparisonType::GreaterThan); - threshold->setComparisonValue(0.1); - thresholdSet.setArrayThresholds({threshold}); - - args.insertOrAssign(MultiThresholdObjectsFilter::k_ArrayThresholdsObject_Key, std::make_any(thresholdSet)); - args.insertOrAssign(MultiThresholdObjectsFilter::k_CreatedDataName_Key, std::make_any(k_ThresholdArrayName)); args.insertOrAssign(MultiThresholdObjectsFilter::k_CreatedMaskType_Key, std::make_any(DataType::uint8)); - // Preflight the filter and check result - auto preflightResult = filter.preflight(dataStructure, args); - SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions) - - // Execute the filter and check the result - auto executeResult = filter.execute(dataStructure, args); - SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result) - - checkMaskValues(dataStructure, k_ThresholdArrayPath); + runMaskTypeFilter(filter, args, dataStructure); } SECTION("UInt16 Threshold") { - MultiThresholdObjectsFilter filter; - Arguments args; - - ArrayThresholdSet thresholdSet; - auto threshold = std::make_shared(); - threshold->setArrayPath(k_TestArrayFloatPath); - threshold->setComparisonType(ArrayThreshold::ComparisonType::GreaterThan); - threshold->setComparisonValue(0.1); - thresholdSet.setArrayThresholds({threshold}); - - args.insertOrAssign(MultiThresholdObjectsFilter::k_ArrayThresholdsObject_Key, std::make_any(thresholdSet)); - args.insertOrAssign(MultiThresholdObjectsFilter::k_CreatedDataName_Key, std::make_any(k_ThresholdArrayName)); args.insertOrAssign(MultiThresholdObjectsFilter::k_CreatedMaskType_Key, std::make_any(DataType::uint16)); - // Preflight the filter and check result - auto preflightResult = filter.preflight(dataStructure, args); - SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions) - - // Execute the filter and check the result - auto executeResult = filter.execute(dataStructure, args); - SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result) - - checkMaskValues(dataStructure, k_ThresholdArrayPath); + runMaskTypeFilter(filter, args, dataStructure); } SECTION("UInt32 Threshold") { - MultiThresholdObjectsFilter filter; - Arguments args; - - ArrayThresholdSet thresholdSet; - auto threshold = std::make_shared(); - threshold->setArrayPath(k_TestArrayFloatPath); - threshold->setComparisonType(ArrayThreshold::ComparisonType::GreaterThan); - threshold->setComparisonValue(0.1); - thresholdSet.setArrayThresholds({threshold}); - - args.insertOrAssign(MultiThresholdObjectsFilter::k_ArrayThresholdsObject_Key, std::make_any(thresholdSet)); - args.insertOrAssign(MultiThresholdObjectsFilter::k_CreatedDataName_Key, std::make_any(k_ThresholdArrayName)); args.insertOrAssign(MultiThresholdObjectsFilter::k_CreatedMaskType_Key, std::make_any(DataType::uint32)); - // Preflight the filter and check result - auto preflightResult = filter.preflight(dataStructure, args); - SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions) - - // Execute the filter and check the result - auto executeResult = filter.execute(dataStructure, args); - SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result) - - checkMaskValues(dataStructure, k_ThresholdArrayPath); + runMaskTypeFilter(filter, args, dataStructure); } SECTION("UInt64 Threshold") { - MultiThresholdObjectsFilter filter; - Arguments args; - - ArrayThresholdSet thresholdSet; - auto threshold = std::make_shared(); - threshold->setArrayPath(k_TestArrayFloatPath); - threshold->setComparisonType(ArrayThreshold::ComparisonType::GreaterThan); - threshold->setComparisonValue(0.1); - thresholdSet.setArrayThresholds({threshold}); - - args.insertOrAssign(MultiThresholdObjectsFilter::k_ArrayThresholdsObject_Key, std::make_any(thresholdSet)); - args.insertOrAssign(MultiThresholdObjectsFilter::k_CreatedDataName_Key, std::make_any(k_ThresholdArrayName)); args.insertOrAssign(MultiThresholdObjectsFilter::k_CreatedMaskType_Key, std::make_any(DataType::uint64)); - // Preflight the filter and check result - auto preflightResult = filter.preflight(dataStructure, args); - SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions) - - // Execute the filter and check the result - auto executeResult = filter.execute(dataStructure, args); - SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result) - - checkMaskValues(dataStructure, k_ThresholdArrayPath); + runMaskTypeFilter(filter, args, dataStructure); } // Floating Point SECTION("Float32 Threshold") { - MultiThresholdObjectsFilter filter; - Arguments args; - - ArrayThresholdSet thresholdSet; - auto threshold = std::make_shared(); - threshold->setArrayPath(k_TestArrayFloatPath); - threshold->setComparisonType(ArrayThreshold::ComparisonType::GreaterThan); - threshold->setComparisonValue(0.1); - thresholdSet.setArrayThresholds({threshold}); - - args.insertOrAssign(MultiThresholdObjectsFilter::k_ArrayThresholdsObject_Key, std::make_any(thresholdSet)); - args.insertOrAssign(MultiThresholdObjectsFilter::k_CreatedDataName_Key, std::make_any(k_ThresholdArrayName)); args.insertOrAssign(MultiThresholdObjectsFilter::k_CreatedMaskType_Key, std::make_any(DataType::float32)); - // Preflight the filter and check result - auto preflightResult = filter.preflight(dataStructure, args); - SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions) - - // Execute the filter and check the result - auto executeResult = filter.execute(dataStructure, args); - SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result) - - checkMaskValues(dataStructure, k_ThresholdArrayPath); + runMaskTypeFilter(filter, args, dataStructure); } SECTION("Float64 Threshold") { - MultiThresholdObjectsFilter filter; - Arguments args; - - ArrayThresholdSet thresholdSet; - auto threshold = std::make_shared(); - threshold->setArrayPath(k_TestArrayFloatPath); - threshold->setComparisonType(ArrayThreshold::ComparisonType::GreaterThan); - threshold->setComparisonValue(0.1); - thresholdSet.setArrayThresholds({threshold}); - - args.insertOrAssign(MultiThresholdObjectsFilter::k_ArrayThresholdsObject_Key, std::make_any(thresholdSet)); - args.insertOrAssign(MultiThresholdObjectsFilter::k_CreatedDataName_Key, std::make_any(k_ThresholdArrayName)); args.insertOrAssign(MultiThresholdObjectsFilter::k_CreatedMaskType_Key, std::make_any(DataType::float64)); - // Preflight the filter and check result - auto preflightResult = filter.preflight(dataStructure, args); - SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions) - - // Execute the filter and check the result - auto executeResult = filter.execute(dataStructure, args); - SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result) - - checkMaskValues(dataStructure, k_ThresholdArrayPath); - } - - UnitTest::CheckArraysInheritTupleDims(dataStructure); -} - -TEST_CASE("SimplnxCore::MultiThresholdObjects: Valid Execution - Multicomponent", "[SimplnxCore][MultiThresholdObjectsFilter]") -{ - DataStructure dataStructure = CreateTestDataStructure(); - - MultiThresholdObjectsFilter filter; - Arguments args; - - ArrayThresholdSet thresholdSet; - auto threshold = std::make_shared(); - threshold->setArrayPath(k_MultiComponentArrayPath); - threshold->setComparisonType(ArrayThreshold::ComparisonType::GreaterThan); - threshold->setComparisonValue(0); - threshold->setComponentIndex(1); - thresholdSet.setArrayThresholds({threshold}); - - args.insertOrAssign(MultiThresholdObjectsFilter::k_ArrayThresholdsObject_Key, std::make_any(thresholdSet)); - args.insertOrAssign(MultiThresholdObjectsFilter::k_CreatedDataName_Key, std::make_any(k_ThresholdArrayName)); - args.insertOrAssign(MultiThresholdObjectsFilter::k_CreatedMaskType_Key, std::make_any(DataType::boolean)); - - // Preflight the filter and check result - auto preflightResult = filter.preflight(dataStructure, args); - SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions) - - // Execute the filter and check the result - auto executeResult = filter.execute(dataStructure, args); - SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result) - - auto* thresholdArray = dataStructure.getDataAs(k_ThresholdArrayPath); - REQUIRE(thresholdArray != nullptr); - - usize numTuples = thresholdArray->getNumberOfTuples(); - - // (x, y, z) - // y > 0 - // even tuple indices should be true except 0 - REQUIRE_FALSE((*thresholdArray)[0]); - for(usize i = 1; i < numTuples; i++) - { - bool value = (*thresholdArray)[i]; - if(i % 2 == 0) - { - REQUIRE(value); - } - else - { - REQUIRE_FALSE(value); - } + runMaskTypeFilter(filter, args, dataStructure); } UnitTest::CheckArraysInheritTupleDims(dataStructure); } - -TEST_CASE("SimplnxCore::MultiThresholdObjectsFilter: SIMPL Backwards Compatibility", "[SimplnxCore][MultiThresholdObjectsFilter][BackwardsCompatibility]") -{ - auto app = Application::GetOrCreateInstance(); - UnitTest::LoadPlugins(); - auto filterList = app->getFilterList(); - - const fs::path conversionDir = fs::path(nx::core::unit_test::k_SourceDir.view()) / "test" / "simpl_conversion"; - - const std::vector> fixtures = { - {"SIMPL 6.5 (UUID)", conversionDir / "6_5" / "MultiThresholdObjectsFilter.json"}, - {"SIMPL 6.4 (Filter_Name)", conversionDir / "6_4" / "MultiThresholdObjectsFilter.json"}, - }; - - for(const auto& [label, fixturePath] : fixtures) - { - DYNAMIC_SECTION(label) - { - auto pipelineResult = Pipeline::FromSIMPLFile(fixturePath, filterList); - REQUIRE(pipelineResult.valid()); - - auto& pipeline = pipelineResult.value(); - REQUIRE(pipeline.size() == 1); - - auto* pipelineFilter = dynamic_cast(pipeline.at(0)); - REQUIRE(pipelineFilter != nullptr); - - const IFilter* filter = pipelineFilter->getFilter(); - REQUIRE(filter != nullptr); - REQUIRE(filter->uuid() == FilterTraits::uuid); - - const Arguments args = pipelineFilter->getArguments(); - CHECK(args.value(MultiThresholdObjectsFilter::k_CreatedDataName_Key) == "TestName"); - } - } -}