From 1ca3b67bfae327b074161829b180a8f847303464 Mon Sep 17 00:00:00 2001 From: Matthew Marine Date: Wed, 22 Apr 2026 07:59:50 -0400 Subject: [PATCH 01/14] WIP: Updating MultiThresholdObjects and unit tests * Updated MultiThresholdObjects algorithm to account for the IsInverted state allowed by individual thresholds. * WIP: Replacing unit tests with smaller datasets and standardized testing functions. Integer and floating point single component DataArrays are tested for all comparison types and inversion states using a single threshold. Multicomponent arrays are in the process of being tested and the filter was updated for assumptions that may be wrong. The documentation and GUI need to be referenced before moving forward. Multicomponent threshold tests and threshold creation will likely need to be adjusted based on new information. * TODO: Create tests for entire threshold sets and even nested sets. --- .../Algorithms/MultiThresholdObjects.cpp | 16 +- .../Filters/MultiThresholdObjectsFilter.cpp | 4 +- .../test/MultiThresholdObjectsTest.cpp | 557 +++++++++++++++++- 3 files changed, 554 insertions(+), 23 deletions(-) diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/MultiThresholdObjects.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/MultiThresholdObjects.cpp index 48ea54b9d7..aac4fece04 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/MultiThresholdObjects.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/MultiThresholdObjects.cpp @@ -15,11 +15,12 @@ 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, std::vector& output, bool isInverted) : m_ComparisonOperator(compType) , m_ComparisonValue(compValue) , m_ComponentIndex(componentIndex) , m_Output(output) + , m_IsInverted(isInverted) { } @@ -31,7 +32,12 @@ 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); + if (m_IsInverted) + { + comparison = !comparison; + } + T outputValue = comparison ? trueValue : falseValue; m_Output[tupleIndex] = outputValue; } } @@ -67,6 +73,7 @@ class ThresholdFilterHelper ArrayThreshold::ComparisonValue m_ComparisonValue; usize m_ComponentIndex = 0; std::vector& m_Output; + bool m_IsInverted = false; }; struct ExecuteThresholdHelper @@ -120,12 +127,13 @@ void ThresholdValue(const ArrayThreshold& comparisonValue, const DataStructure& nx::core::ArrayThreshold::ComparisonType compOperator = comparisonValue.getComparisonType(); nx::core::ArrayThreshold::ComparisonValue compValue = comparisonValue.getComparisonValue(); nx::core::IArrayThreshold::UnionOperator unionOperator = comparisonValue.getUnionOperator(); + bool isInverted = comparisonValue.isInverted(); DataPath inputDataArrayPath = comparisonValue.getArrayPath(); usize componentIndex = comparisonValue.getComponentIndex(); - ThresholdFilterHelper helper(compOperator, compValue, componentIndex, tempResultVector); + ThresholdFilterHelper helper(compOperator, compValue, componentIndex, tempResultVector, isInverted); const auto& iDataArray = dataStructure.getDataRefAs(inputDataArrayPath); @@ -259,12 +267,14 @@ Result<> MultiThresholdObjects::operator()() const IArrayThreshold* thresholdPtr = threshold.get(); if(const auto* comparisonSet = dynamic_cast(thresholdPtr); comparisonSet != nullptr) { + // Do not replace values on first threshold, update firstValueFound to reflect that a threshold has been run. 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) { + // Do not replace values on first threshold, update firstValueFound to reflect that a threshold has been run. ExecuteDataFunction(ThresholdValueFunctor{}, maskArrayType, *comparisonValue, m_DataStructure, m_DataStructure.getDataRefAs(maskArrayPath), err, !firstValueFound, thresholdsObject.isInverted(), trueValue, falseValue); firstValueFound = true; diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/MultiThresholdObjectsFilter.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/MultiThresholdObjectsFilter.cpp index 85c1535dc6..d0840085b3 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/MultiThresholdObjectsFilter.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/MultiThresholdObjectsFilter.cpp @@ -223,8 +223,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(), dataStore.getComponentShape(), 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..e3fad80dae 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,329 @@ 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++) + // 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++) { - 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; + (*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; } +ArrayThresholdSet CreateSingleThreshold(const DataPath& arrayPath, ArrayThreshold::ComparisonType comparisonType, double value, bool isInverted) +{ + ArrayThresholdSet thresholdSet; + auto threshold = std::make_shared(); + threshold->setArrayPath(arrayPath); + threshold->setComparisonType(comparisonType); + threshold->setComparisonValue(value); + threshold->setInverted(isInverted); + thresholdSet.setArrayThresholds({threshold}); + + return thresholdSet; +} + +void RunSingleThresholdTest(DataStructure& dataStructure, const DataPath& arrayPath, ArrayThreshold::ComparisonType comparisonType, double value, bool isInverted) +{ + MultiThresholdObjectsFilter filter; + Arguments args; + + auto thresholdSet = CreateSingleThreshold(arrayPath, comparisonType, value, 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)); + + // 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 the input and output arrays to have an equal number of components + const auto* thresholdArrayPtr = dataStructure.getDataAs(k_ThresholdArrayPath); + REQUIRE(thresholdArrayPtr != nullptr); + + const auto* inputArrayPtr = dataStructure.getDataAs(arrayPath); + REQUIRE(inputArrayPtr->getNumberOfComponents() == thresholdArrayPtr->getNumberOfComponents()); +} + +void CheckIntTestDataGreaterThanSingleComponent(const DataStructure& dataStructure, 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++) + { + bool value = thresholdStore[i]; + bool expected = InputIntValue(i) > thresholdValue; + + if(isInverted) + { + expected = !expected; + } + + REQUIRE(value == expected); + } +} + +void CheckFloatTestDataGreaterThanSingleComponent(const DataStructure& dataStructure, 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++) + { + bool value = thresholdStore[i]; + bool expected = InputFloatValue(i) > thresholdValue; + + if(isInverted) + { + expected = !expected; + } + + REQUIRE(value == expected); + } +} + +void CheckIntTestDataGreaterThanMultiComponent(const DataStructure& dataStructure, 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++) + { + for(usize j = 0; j < k_MultiComponentCount; j++) + { + bool value = thresholdStore[i * k_MultiComponentCount + j]; + bool expected = InputIntComponentValue(i, j) > thresholdValue; + + if(isInverted) + { + expected = !expected; + } + + REQUIRE(value == expected); + } + } +} + +void CheckIntTestDataLessThanSingleComponent(const DataStructure& dataStructure, 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++) + { + bool value = thresholdStore[i]; + bool expected = i < static_cast(thresholdValue); + + if(isInverted) + { + expected = !expected; + } + + REQUIRE(value == expected); + } +} + +void CheckFloatTestDataLessThanSingleComponent(const DataStructure& dataStructure, 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++) + { + float64 expectedValue = InputFloatValue(i); + bool value = thresholdStore[i]; + bool expected = InputFloatValue(i) < thresholdValue; + + if(isInverted) + { + expected = !expected; + } + + REQUIRE(value == expected); + } +} + +void CheckIntTestDataLessThanMultiComponent(const DataStructure& dataStructure, 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++) + { + for(usize j = 0; j < k_MultiComponentCount; j++) + { + bool value = thresholdStore[i * k_MultiComponentCount + j]; + bool expected = InputIntComponentValue(i, j) < thresholdValue; + + if(isInverted) + { + expected = !expected; + } + + REQUIRE(value == expected); + } + } +} + +void CheckIntTestDataEqualToSingleComponent(const DataStructure& dataStructure, 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++) + { + bool value = thresholdStore[i]; + bool expected = i == static_cast(thresholdValue); + + if(isInverted) + { + expected = !expected; + } + + REQUIRE(value == expected); + } +} + +void CheckIntTestDataEqualToMultiComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted) +{ + const auto* thresholdArrayPtr = dataStructure.getDataAs(k_ThresholdArrayPath); + REQUIRE(thresholdArrayPtr != nullptr); + + auto& thresholdStore = thresholdArrayPtr->getDataStoreRef(); + + const auto& inputStore = dataStructure.getDataAs(k_MultiComponentArrayPath)->getDataStoreRef(); + + for(usize i = 0; i < k_TupleCount; i++) + { + for(usize j = 0; j < k_MultiComponentCount; j++) + { + int32 inputValue = inputStore[i * k_MultiComponentCount + j]; + bool value = thresholdStore[i * k_MultiComponentCount + j]; + bool expected = InputIntComponentValue(i, j) == thresholdValue; + + if(isInverted) + { + expected = !expected; + } + + //REQUIRE(value == expected); + } + } +} + +void CheckFloatTestDataEqualToSingleComponent(const DataStructure& dataStructure, 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++) + { + bool value = thresholdStore[i]; + bool expected = InputFloatValue(i) == thresholdValue; + + if(isInverted) + { + expected = !expected; + } + + REQUIRE(value == expected); + } +} + +void CheckIntTestDataNotEqualToSingleComponent(const DataStructure& dataStructure, 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++) + { + bool value = thresholdStore[i]; + bool expected = i != thresholdValue; + + if(isInverted) + { + expected = !expected; + } + + REQUIRE(value == expected); + } +} + +void CheckIntTestDataNotEqualToMultiComponent(const DataStructure& dataStructure, 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++) + { + for(usize j = 0; j < k_MultiComponentCount; j++) + { + bool value = thresholdStore[i * k_MultiComponentCount + j]; + bool expected = InputIntComponentValue(i, j) != thresholdValue; + if(isInverted) + { + expected = !expected; + } + + REQUIRE(value == expected); + } + } +} + +void CheckFloatTestDataNotEqualToSingleComponent(const DataStructure& dataStructure, 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++) + { + bool value = thresholdStore[i]; + bool expected = InputFloatValue(i) != thresholdValue; + if(isInverted) + { + expected = !expected; + } + + REQUIRE(value == expected); + } +} + template float64 GetOutOfBoundsMinimumValue() { @@ -97,6 +420,203 @@ float64 GetOutOfBoundsMaximumValue() } } // namespace +TEST_CASE("SimplnxCore::MultiThresholdObjects: Valid Single Thresholds: Int", "[SimplnxCore][MultiThresholdObjectsFilter]") +{ + UnitTest::LoadPlugins(); + + DataStructure dataStructure = CreateTestDataStructure(); + const DataPath targetArray = k_TestArrayIntPath; + bool isInverted = false; + + SECTION("ArrayThreshold: >") + { + const double thresholdValue = 2.0; + RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::GreaterThan, thresholdValue, isInverted); + CheckIntTestDataGreaterThanSingleComponent(dataStructure, thresholdValue, isInverted); + } + + SECTION("ArrayThreshold: <") + { + const double thresholdValue = 3.0; + RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::LessThan, thresholdValue, isInverted); + CheckIntTestDataLessThanSingleComponent(dataStructure, thresholdValue, isInverted); + } + + SECTION("ArrayThreshold: ==") + { + const double thresholdValue = 3.0; + RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::Operator_Equal, thresholdValue, isInverted); + CheckIntTestDataEqualToSingleComponent(dataStructure, thresholdValue, isInverted); + CheckIntTestDataNotEqualToSingleComponent(dataStructure, thresholdValue, !isInverted); + } + SECTION("ArrayThreshold: !=") + { + const double thresholdValue = 4.0; + RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::Operator_NotEqual, thresholdValue, isInverted); + CheckIntTestDataEqualToSingleComponent(dataStructure, thresholdValue, !isInverted); + CheckIntTestDataNotEqualToSingleComponent(dataStructure, thresholdValue, isInverted); + } +} + +TEST_CASE("SimplnxCore::MultiThresholdObjects: Valid Single Thresholds: Int Inverted", "[SimplnxCore][MultiThresholdObjectsFilter]") +{ + UnitTest::LoadPlugins(); + + DataStructure dataStructure = CreateTestDataStructure(); + const DataPath targetArray = k_TestArrayIntPath; + bool isInverted = true; + + SECTION("ArrayThreshold: >") + { + const double thresholdValue = 2.0; + RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::GreaterThan, thresholdValue, isInverted); + CheckIntTestDataGreaterThanSingleComponent(dataStructure, thresholdValue, isInverted); + } + + SECTION("ArrayThreshold: <") + { + const double thresholdValue = 3.0; + RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::LessThan, thresholdValue, isInverted); + CheckIntTestDataLessThanSingleComponent(dataStructure, thresholdValue, isInverted); + } + + SECTION("ArrayThreshold: ==") + { + const double thresholdValue = 3.0; + RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::Operator_Equal, thresholdValue, isInverted); + CheckIntTestDataEqualToSingleComponent(dataStructure, thresholdValue, isInverted); + CheckIntTestDataNotEqualToSingleComponent(dataStructure, thresholdValue, !isInverted); + } + SECTION("ArrayThreshold: !=") + { + const double thresholdValue = 4.0; + RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::Operator_NotEqual, thresholdValue, isInverted); + CheckIntTestDataEqualToSingleComponent(dataStructure, thresholdValue, !isInverted); + CheckIntTestDataNotEqualToSingleComponent(dataStructure, thresholdValue, isInverted); + } +} + +TEST_CASE("SimplnxCore::MultiThresholdObjects: Valid Single Thresholds: Float", "[SimplnxCore][MultiThresholdObjectsFilter]") +{ + UnitTest::LoadPlugins(); + + DataStructure dataStructure = CreateTestDataStructure(); + const DataPath targetArray = k_TestArrayFloatPath; + bool isInverted = false; + + // RunSingleComponentThresholdTests(dataStructure, k_TestArrayIntPath, 3.0, false); + SECTION("ArrayThreshold: >") + { + const double thresholdValue = 0.04; + RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::GreaterThan, thresholdValue, isInverted); + CheckFloatTestDataGreaterThanSingleComponent(dataStructure, thresholdValue, isInverted); + } + + SECTION("ArrayThreshold: <") + { + const double thresholdValue = 0.02; + RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::LessThan, thresholdValue, isInverted); + CheckFloatTestDataLessThanSingleComponent(dataStructure, thresholdValue, isInverted); + } + + SECTION("ArrayThreshold: ==") + { + const double thresholdValue = 0.03; + RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::Operator_Equal, thresholdValue, isInverted); + CheckFloatTestDataEqualToSingleComponent(dataStructure, thresholdValue, isInverted); + CheckFloatTestDataNotEqualToSingleComponent(dataStructure, thresholdValue, !isInverted); + } + SECTION("ArrayThreshold: !=") + { + const double thresholdValue = 0.02; + RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::Operator_NotEqual, thresholdValue, isInverted); + CheckFloatTestDataEqualToSingleComponent(dataStructure, thresholdValue, !isInverted); + CheckFloatTestDataNotEqualToSingleComponent(dataStructure, thresholdValue, isInverted); + } +} + +TEST_CASE("SimplnxCore::MultiThresholdObjects: Valid Single Thresholds: Float Inverted", "[SimplnxCore][MultiThresholdObjectsFilter]") +{ + UnitTest::LoadPlugins(); + + DataStructure dataStructure = CreateTestDataStructure(); + const DataPath targetArray = k_TestArrayFloatPath; + bool isInverted = true; + + // RunSingleComponentThresholdTests(dataStructure, k_TestArrayIntPath, 3.0, false); + SECTION("ArrayThreshold: >") + { + const double thresholdValue = 0.02; + RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::GreaterThan, thresholdValue, isInverted); + CheckFloatTestDataGreaterThanSingleComponent(dataStructure, thresholdValue, isInverted); + } + + SECTION("ArrayThreshold: <") + { + const double thresholdValue = 0.03; + RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::LessThan, thresholdValue, isInverted); + CheckFloatTestDataLessThanSingleComponent(dataStructure, thresholdValue, isInverted); + } + + SECTION("ArrayThreshold: ==") + { + const double thresholdValue = 0.02; + RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::Operator_Equal, thresholdValue, isInverted); + CheckFloatTestDataEqualToSingleComponent(dataStructure, thresholdValue, isInverted); + CheckFloatTestDataNotEqualToSingleComponent(dataStructure, thresholdValue, !isInverted); + } + SECTION("ArrayThreshold: !=") + { + const double thresholdValue = 0.01; + RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::Operator_NotEqual, thresholdValue, isInverted); + CheckFloatTestDataEqualToSingleComponent(dataStructure, thresholdValue, !isInverted); + CheckFloatTestDataNotEqualToSingleComponent(dataStructure, thresholdValue, isInverted); + } +} + +TEST_CASE("SimplnxCore::MultiThresholdObjects: Valid Single Thresholds: Int Multi-Component", "[SimplnxCore][MultiThresholdObjectsFilter]") +{ + UnitTest::LoadPlugins(); + + DataStructure dataStructure = CreateTestDataStructure(); + const DataPath targetArray = k_MultiComponentArrayPath; + bool isInverted = false; + + SECTION("ArrayThreshold: >") + { + const double thresholdValue = 2.0; + RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::GreaterThan, thresholdValue, isInverted); + CheckIntTestDataGreaterThanMultiComponent(dataStructure, thresholdValue, isInverted); + } + + SECTION("ArrayThreshold: <") + { + const double thresholdValue = 3.0; + RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::LessThan, thresholdValue, isInverted); + CheckIntTestDataLessThanMultiComponent(dataStructure, thresholdValue, isInverted); + } + + SECTION("ArrayThreshold: ==") + { + const double thresholdValue = 3.0; + RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::Operator_Equal, thresholdValue, isInverted); + CheckIntTestDataEqualToMultiComponent(dataStructure, thresholdValue, isInverted); + CheckIntTestDataNotEqualToMultiComponent(dataStructure, thresholdValue, !isInverted); + } + SECTION("ArrayThreshold: !=") + { + const double thresholdValue = 4.0; + RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::Operator_NotEqual, thresholdValue, isInverted); + CheckIntTestDataEqualToMultiComponent(dataStructure, thresholdValue, !isInverted); + CheckIntTestDataNotEqualToMultiComponent(dataStructure, thresholdValue, isInverted); + } +} + +/// +/// /////// +/// + +#if false TEST_CASE("SimplnxCore::MultiThresholdObjects: Valid Execution", "[SimplnxCore][MultiThresholdObjectsFilter]") { UnitTest::LoadPlugins(); @@ -779,6 +1299,7 @@ TEST_CASE("SimplnxCore::MultiThresholdObjects: Valid Execution - Multicomponent" UnitTest::CheckArraysInheritTupleDims(dataStructure); } +#endif TEST_CASE("SimplnxCore::MultiThresholdObjectsFilter: SIMPL Backwards Compatibility", "[SimplnxCore][MultiThresholdObjectsFilter][BackwardsCompatibility]") { From 6d14d0c697795c4083634e708ba08d2dd4477d72 Mon Sep 17 00:00:00 2001 From: Matthew Marine Date: Wed, 22 Apr 2026 12:01:53 -0400 Subject: [PATCH 02/14] Multi-component count correction * Mask array is always 1 component. * Update unit tests for multicomponent array thresholds --- .../Filters/MultiThresholdObjectsFilter.cpp | 2 +- .../test/MultiThresholdObjectsTest.cpp | 141 +++++++++--------- 2 files changed, 70 insertions(+), 73 deletions(-) diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/MultiThresholdObjectsFilter.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/MultiThresholdObjectsFilter.cpp index d0840085b3..9ebe5b283d 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/MultiThresholdObjectsFilter.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/MultiThresholdObjectsFilter.cpp @@ -224,7 +224,7 @@ IFilter::PreflightResult MultiThresholdObjectsFilter::preflightImpl(const DataSt // Create the output boolean array const auto& dataStore = dataArray.getIDataStoreRef(); - auto action = std::make_unique(maskArrayType, dataStore.getTupleShape(), dataStore.getComponentShape(), firstDataPath.replaceName(maskArrayName), dataArray.getDataFormat()); + 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 e3fad80dae..519bb174e2 100644 --- a/src/Plugins/SimplnxCore/test/MultiThresholdObjectsTest.cpp +++ b/src/Plugins/SimplnxCore/test/MultiThresholdObjectsTest.cpp @@ -92,25 +92,26 @@ DataStructure CreateTestDataStructure() return dataStructure; } -ArrayThresholdSet CreateSingleThreshold(const DataPath& arrayPath, ArrayThreshold::ComparisonType comparisonType, double value, bool isInverted) +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; } -void RunSingleThresholdTest(DataStructure& dataStructure, const DataPath& arrayPath, ArrayThreshold::ComparisonType comparisonType, double value, bool isInverted) +void RunSingleThresholdTest(DataStructure& dataStructure, const DataPath& arrayPath, ArrayThreshold::ComparisonType comparisonType, double value, bool isInverted, int32 componentIndex = 0) { MultiThresholdObjectsFilter filter; Arguments args; - auto thresholdSet = CreateSingleThreshold(arrayPath, comparisonType, value, isInverted); + auto thresholdSet = CreateSingleThreshold(arrayPath, comparisonType, value, isInverted, componentIndex); args.insertOrAssign(MultiThresholdObjectsFilter::k_ArrayThresholdsObject_Key, std::make_any(thresholdSet)); args.insertOrAssign(MultiThresholdObjectsFilter::k_CreatedDataName_Key, std::make_any(k_ThresholdArrayName)); @@ -124,12 +125,11 @@ void RunSingleThresholdTest(DataStructure& dataStructure, const DataPath& arrayP auto executeResult = filter.execute(dataStructure, args); SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result) - // Require the input and output arrays to have an equal number of components + // Require that the mask array only has one component const auto* thresholdArrayPtr = dataStructure.getDataAs(k_ThresholdArrayPath); REQUIRE(thresholdArrayPtr != nullptr); - const auto* inputArrayPtr = dataStructure.getDataAs(arrayPath); - REQUIRE(inputArrayPtr->getNumberOfComponents() == thresholdArrayPtr->getNumberOfComponents()); + REQUIRE(thresholdArrayPtr->getNumberOfComponents() == 1); } void CheckIntTestDataGreaterThanSingleComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted) @@ -174,7 +174,7 @@ void CheckFloatTestDataGreaterThanSingleComponent(const DataStructure& dataStruc } } -void CheckIntTestDataGreaterThanMultiComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted) +void CheckIntTestDataLessThanSingleComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted) { const auto* thresholdArrayPtr = dataStructure.getDataAs(k_ThresholdArrayPath); REQUIRE(thresholdArrayPtr != nullptr); @@ -183,22 +183,19 @@ void CheckIntTestDataGreaterThanMultiComponent(const DataStructure& dataStructur for(usize i = 0; i < k_TupleCount; i++) { - for(usize j = 0; j < k_MultiComponentCount; j++) - { - bool value = thresholdStore[i * k_MultiComponentCount + j]; - bool expected = InputIntComponentValue(i, j) > thresholdValue; - - if(isInverted) - { - expected = !expected; - } + bool value = thresholdStore[i]; + bool expected = i < static_cast(thresholdValue); - REQUIRE(value == expected); + if(isInverted) + { + expected = !expected; } + + REQUIRE(value == expected); } } -void CheckIntTestDataLessThanSingleComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted) +void CheckFloatTestDataLessThanSingleComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted) { const auto* thresholdArrayPtr = dataStructure.getDataAs(k_ThresholdArrayPath); REQUIRE(thresholdArrayPtr != nullptr); @@ -207,8 +204,9 @@ void CheckIntTestDataLessThanSingleComponent(const DataStructure& dataStructure, for(usize i = 0; i < k_TupleCount; i++) { + float64 expectedValue = InputFloatValue(i); bool value = thresholdStore[i]; - bool expected = i < static_cast(thresholdValue); + bool expected = InputFloatValue(i) < thresholdValue; if(isInverted) { @@ -219,7 +217,7 @@ void CheckIntTestDataLessThanSingleComponent(const DataStructure& dataStructure, } } -void CheckFloatTestDataLessThanSingleComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted) +void CheckIntTestDataEqualToSingleComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted) { const auto* thresholdArrayPtr = dataStructure.getDataAs(k_ThresholdArrayPath); REQUIRE(thresholdArrayPtr != nullptr); @@ -228,9 +226,8 @@ void CheckFloatTestDataLessThanSingleComponent(const DataStructure& dataStructur for(usize i = 0; i < k_TupleCount; i++) { - float64 expectedValue = InputFloatValue(i); bool value = thresholdStore[i]; - bool expected = InputFloatValue(i) < thresholdValue; + bool expected = i == static_cast(thresholdValue); if(isInverted) { @@ -241,7 +238,7 @@ void CheckFloatTestDataLessThanSingleComponent(const DataStructure& dataStructur } } -void CheckIntTestDataLessThanMultiComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted) +void CheckFloatTestDataEqualToSingleComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted) { const auto* thresholdArrayPtr = dataStructure.getDataAs(k_ThresholdArrayPath); REQUIRE(thresholdArrayPtr != nullptr); @@ -250,22 +247,19 @@ void CheckIntTestDataLessThanMultiComponent(const DataStructure& dataStructure, for(usize i = 0; i < k_TupleCount; i++) { - for(usize j = 0; j < k_MultiComponentCount; j++) - { - bool value = thresholdStore[i * k_MultiComponentCount + j]; - bool expected = InputIntComponentValue(i, j) < thresholdValue; - - if(isInverted) - { - expected = !expected; - } + bool value = thresholdStore[i]; + bool expected = InputFloatValue(i) == thresholdValue; - REQUIRE(value == expected); + if(isInverted) + { + expected = !expected; } + + REQUIRE(value == expected); } } -void CheckIntTestDataEqualToSingleComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted) +void CheckIntTestDataNotEqualToSingleComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted) { const auto* thresholdArrayPtr = dataStructure.getDataAs(k_ThresholdArrayPath); REQUIRE(thresholdArrayPtr != nullptr); @@ -275,7 +269,7 @@ void CheckIntTestDataEqualToSingleComponent(const DataStructure& dataStructure, for(usize i = 0; i < k_TupleCount; i++) { bool value = thresholdStore[i]; - bool expected = i == static_cast(thresholdValue); + bool expected = i != thresholdValue; if(isInverted) { @@ -286,34 +280,31 @@ void CheckIntTestDataEqualToSingleComponent(const DataStructure& dataStructure, } } -void CheckIntTestDataEqualToMultiComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted) +// Multi-component checks + +void CheckIntTestDataGreaterThanMultiComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted, int32 componentIndex) { const auto* thresholdArrayPtr = dataStructure.getDataAs(k_ThresholdArrayPath); REQUIRE(thresholdArrayPtr != nullptr); auto& thresholdStore = thresholdArrayPtr->getDataStoreRef(); - const auto& inputStore = dataStructure.getDataAs(k_MultiComponentArrayPath)->getDataStoreRef(); - for(usize i = 0; i < k_TupleCount; i++) { - for(usize j = 0; j < k_MultiComponentCount; j++) - { - int32 inputValue = inputStore[i * k_MultiComponentCount + j]; - bool value = thresholdStore[i * k_MultiComponentCount + j]; - bool expected = InputIntComponentValue(i, j) == thresholdValue; + usize arrayIndex = i * k_MultiComponentCount + componentIndex; + bool value = thresholdStore[arrayIndex]; + bool expected = InputIntComponentValue(i, componentIndex) > thresholdValue; - if(isInverted) - { - expected = !expected; - } - - //REQUIRE(value == expected); + if(isInverted) + { + expected = !expected; } + + REQUIRE(value == expected); } } -void CheckFloatTestDataEqualToSingleComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted) +void CheckIntTestDataLessThanMultiComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted, int32 componentIndex) { const auto* thresholdArrayPtr = dataStructure.getDataAs(k_ThresholdArrayPath); REQUIRE(thresholdArrayPtr != nullptr); @@ -322,8 +313,9 @@ void CheckFloatTestDataEqualToSingleComponent(const DataStructure& dataStructure for(usize i = 0; i < k_TupleCount; i++) { - bool value = thresholdStore[i]; - bool expected = InputFloatValue(i) == thresholdValue; + usize arrayIndex = i * k_MultiComponentCount + componentIndex; + bool value = thresholdStore[arrayIndex]; + bool expected = InputIntComponentValue(i, componentIndex) < thresholdValue; if(isInverted) { @@ -334,17 +326,21 @@ void CheckFloatTestDataEqualToSingleComponent(const DataStructure& dataStructure } } -void CheckIntTestDataNotEqualToSingleComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted) +void CheckIntTestDataEqualToMultiComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted, int32 componentIndex) { const auto* thresholdArrayPtr = dataStructure.getDataAs(k_ThresholdArrayPath); REQUIRE(thresholdArrayPtr != nullptr); auto& thresholdStore = thresholdArrayPtr->getDataStoreRef(); + const auto& inputStore = dataStructure.getDataAs(k_MultiComponentArrayPath)->getDataStoreRef(); + for(usize i = 0; i < k_TupleCount; i++) { - bool value = thresholdStore[i]; - bool expected = i != thresholdValue; + usize arrayIndex = i * k_MultiComponentCount + componentIndex; + int32 inputValue = inputStore[arrayIndex]; // store value for breakpoint testing purposes. + bool value = thresholdStore[arrayIndex]; + bool expected = InputIntComponentValue(i, componentIndex) == thresholdValue; if(isInverted) { @@ -355,7 +351,7 @@ void CheckIntTestDataNotEqualToSingleComponent(const DataStructure& dataStructur } } -void CheckIntTestDataNotEqualToMultiComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted) +void CheckIntTestDataNotEqualToMultiComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted, int32 componentIndex) { const auto* thresholdArrayPtr = dataStructure.getDataAs(k_ThresholdArrayPath); REQUIRE(thresholdArrayPtr != nullptr); @@ -364,17 +360,16 @@ void CheckIntTestDataNotEqualToMultiComponent(const DataStructure& dataStructure for(usize i = 0; i < k_TupleCount; i++) { - for(usize j = 0; j < k_MultiComponentCount; j++) - { - bool value = thresholdStore[i * k_MultiComponentCount + j]; - bool expected = InputIntComponentValue(i, j) != thresholdValue; - if(isInverted) - { - expected = !expected; - } + usize arrayIndex = i * k_MultiComponentCount + componentIndex; + bool value = thresholdStore[arrayIndex]; + bool expected = InputIntComponentValue(i, componentIndex) != thresholdValue; - REQUIRE(value == expected); + if(isInverted) + { + expected = !expected; } + + REQUIRE(value == expected); } } @@ -389,6 +384,7 @@ void CheckFloatTestDataNotEqualToSingleComponent(const DataStructure& dataStruct { bool value = thresholdStore[i]; bool expected = InputFloatValue(i) != thresholdValue; + if(isInverted) { expected = !expected; @@ -581,34 +577,35 @@ TEST_CASE("SimplnxCore::MultiThresholdObjects: Valid Single Thresholds: Int Mult DataStructure dataStructure = CreateTestDataStructure(); const DataPath targetArray = k_MultiComponentArrayPath; bool isInverted = false; + int32 componentIndex = GENERATE(0, 1, 2); SECTION("ArrayThreshold: >") { const double thresholdValue = 2.0; - RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::GreaterThan, thresholdValue, isInverted); - CheckIntTestDataGreaterThanMultiComponent(dataStructure, thresholdValue, isInverted); + RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::GreaterThan, thresholdValue, isInverted, componentIndex); + CheckIntTestDataGreaterThanMultiComponent(dataStructure, thresholdValue, isInverted, componentIndex); } SECTION("ArrayThreshold: <") { const double thresholdValue = 3.0; RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::LessThan, thresholdValue, isInverted); - CheckIntTestDataLessThanMultiComponent(dataStructure, thresholdValue, isInverted); + CheckIntTestDataLessThanMultiComponent(dataStructure, thresholdValue, isInverted, componentIndex); } SECTION("ArrayThreshold: ==") { const double thresholdValue = 3.0; RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::Operator_Equal, thresholdValue, isInverted); - CheckIntTestDataEqualToMultiComponent(dataStructure, thresholdValue, isInverted); - CheckIntTestDataNotEqualToMultiComponent(dataStructure, thresholdValue, !isInverted); + CheckIntTestDataEqualToMultiComponent(dataStructure, thresholdValue, isInverted, componentIndex); + CheckIntTestDataNotEqualToMultiComponent(dataStructure, thresholdValue, !isInverted, componentIndex); } SECTION("ArrayThreshold: !=") { const double thresholdValue = 4.0; RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::Operator_NotEqual, thresholdValue, isInverted); - CheckIntTestDataEqualToMultiComponent(dataStructure, thresholdValue, !isInverted); - CheckIntTestDataNotEqualToMultiComponent(dataStructure, thresholdValue, isInverted); + CheckIntTestDataEqualToMultiComponent(dataStructure, thresholdValue, !isInverted, componentIndex); + CheckIntTestDataNotEqualToMultiComponent(dataStructure, thresholdValue, isInverted, componentIndex); } } From d47ad775e329e191f7921a95ae6fc0727e439c82 Mon Sep 17 00:00:00 2001 From: Matthew Marine Date: Wed, 22 Apr 2026 12:46:49 -0400 Subject: [PATCH 03/14] Updated single threshold tests * Consolidated unit tests of the same array type and component count using GENERATE. * Added additional value checks. * All single threshold tests pass. --- .../test/MultiThresholdObjectsTest.cpp | 189 ++++++------------ 1 file changed, 58 insertions(+), 131 deletions(-) diff --git a/src/Plugins/SimplnxCore/test/MultiThresholdObjectsTest.cpp b/src/Plugins/SimplnxCore/test/MultiThresholdObjectsTest.cpp index 519bb174e2..25728ada0d 100644 --- a/src/Plugins/SimplnxCore/test/MultiThresholdObjectsTest.cpp +++ b/src/Plugins/SimplnxCore/test/MultiThresholdObjectsTest.cpp @@ -92,6 +92,14 @@ DataStructure CreateTestDataStructure() 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; @@ -106,6 +114,15 @@ ArrayThresholdSet CreateSingleThreshold(const DataPath& arrayPath, ArrayThreshol return thresholdSet; } +/** + * @brief + * @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) { MultiThresholdObjectsFilter filter; @@ -132,6 +149,7 @@ void RunSingleThresholdTest(DataStructure& dataStructure, const DataPath& arrayP REQUIRE(thresholdArrayPtr->getNumberOfComponents() == 1); } +// Integer checks void CheckIntTestDataGreaterThanSingleComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted) { const auto* thresholdArrayPtr = dataStructure.getDataAs(k_ThresholdArrayPath); @@ -153,7 +171,7 @@ void CheckIntTestDataGreaterThanSingleComponent(const DataStructure& dataStructu } } -void CheckFloatTestDataGreaterThanSingleComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted) +void CheckIntTestDataLessThanSingleComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted) { const auto* thresholdArrayPtr = dataStructure.getDataAs(k_ThresholdArrayPath); REQUIRE(thresholdArrayPtr != nullptr); @@ -163,7 +181,7 @@ void CheckFloatTestDataGreaterThanSingleComponent(const DataStructure& dataStruc for(usize i = 0; i < k_TupleCount; i++) { bool value = thresholdStore[i]; - bool expected = InputFloatValue(i) > thresholdValue; + bool expected = InputIntValue(i) < thresholdValue; if(isInverted) { @@ -174,7 +192,7 @@ void CheckFloatTestDataGreaterThanSingleComponent(const DataStructure& dataStruc } } -void CheckIntTestDataLessThanSingleComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted) +void CheckIntTestDataEqualToSingleComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted) { const auto* thresholdArrayPtr = dataStructure.getDataAs(k_ThresholdArrayPath); REQUIRE(thresholdArrayPtr != nullptr); @@ -184,7 +202,7 @@ void CheckIntTestDataLessThanSingleComponent(const DataStructure& dataStructure, for(usize i = 0; i < k_TupleCount; i++) { bool value = thresholdStore[i]; - bool expected = i < static_cast(thresholdValue); + bool expected = InputIntValue(i) == thresholdValue; if(isInverted) { @@ -195,7 +213,7 @@ void CheckIntTestDataLessThanSingleComponent(const DataStructure& dataStructure, } } -void CheckFloatTestDataLessThanSingleComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted) +void CheckIntTestDataNotEqualToSingleComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted) { const auto* thresholdArrayPtr = dataStructure.getDataAs(k_ThresholdArrayPath); REQUIRE(thresholdArrayPtr != nullptr); @@ -204,9 +222,8 @@ void CheckFloatTestDataLessThanSingleComponent(const DataStructure& dataStructur for(usize i = 0; i < k_TupleCount; i++) { - float64 expectedValue = InputFloatValue(i); bool value = thresholdStore[i]; - bool expected = InputFloatValue(i) < thresholdValue; + bool expected = InputIntValue(i) != thresholdValue; if(isInverted) { @@ -217,7 +234,9 @@ void CheckFloatTestDataLessThanSingleComponent(const DataStructure& dataStructur } } -void CheckIntTestDataEqualToSingleComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted) +// Floating point checks + +void CheckFloatTestDataGreaterThanSingleComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted) { const auto* thresholdArrayPtr = dataStructure.getDataAs(k_ThresholdArrayPath); REQUIRE(thresholdArrayPtr != nullptr); @@ -227,7 +246,7 @@ void CheckIntTestDataEqualToSingleComponent(const DataStructure& dataStructure, for(usize i = 0; i < k_TupleCount; i++) { bool value = thresholdStore[i]; - bool expected = i == static_cast(thresholdValue); + bool expected = InputFloatValue(i) > thresholdValue; if(isInverted) { @@ -238,7 +257,7 @@ void CheckIntTestDataEqualToSingleComponent(const DataStructure& dataStructure, } } -void CheckFloatTestDataEqualToSingleComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted) +void CheckFloatTestDataLessThanSingleComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted) { const auto* thresholdArrayPtr = dataStructure.getDataAs(k_ThresholdArrayPath); REQUIRE(thresholdArrayPtr != nullptr); @@ -247,8 +266,9 @@ void CheckFloatTestDataEqualToSingleComponent(const DataStructure& dataStructure for(usize i = 0; i < k_TupleCount; i++) { + float64 expectedValue = InputFloatValue(i); bool value = thresholdStore[i]; - bool expected = InputFloatValue(i) == thresholdValue; + bool expected = InputFloatValue(i) < thresholdValue; if(isInverted) { @@ -259,7 +279,7 @@ void CheckFloatTestDataEqualToSingleComponent(const DataStructure& dataStructure } } -void CheckIntTestDataNotEqualToSingleComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted) +void CheckFloatTestDataEqualToSingleComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted) { const auto* thresholdArrayPtr = dataStructure.getDataAs(k_ThresholdArrayPath); REQUIRE(thresholdArrayPtr != nullptr); @@ -269,7 +289,7 @@ void CheckIntTestDataNotEqualToSingleComponent(const DataStructure& dataStructur for(usize i = 0; i < k_TupleCount; i++) { bool value = thresholdStore[i]; - bool expected = i != thresholdValue; + bool expected = InputFloatValue(i) == thresholdValue; if(isInverted) { @@ -280,9 +300,7 @@ void CheckIntTestDataNotEqualToSingleComponent(const DataStructure& dataStructur } } -// Multi-component checks - -void CheckIntTestDataGreaterThanMultiComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted, int32 componentIndex) +void CheckFloatTestDataNotEqualToSingleComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted) { const auto* thresholdArrayPtr = dataStructure.getDataAs(k_ThresholdArrayPath); REQUIRE(thresholdArrayPtr != nullptr); @@ -291,9 +309,8 @@ void CheckIntTestDataGreaterThanMultiComponent(const DataStructure& dataStructur for(usize i = 0; i < k_TupleCount; i++) { - usize arrayIndex = i * k_MultiComponentCount + componentIndex; - bool value = thresholdStore[arrayIndex]; - bool expected = InputIntComponentValue(i, componentIndex) > thresholdValue; + bool value = thresholdStore[i]; + bool expected = InputFloatValue(i) != thresholdValue; if(isInverted) { @@ -304,7 +321,9 @@ void CheckIntTestDataGreaterThanMultiComponent(const DataStructure& dataStructur } } -void CheckIntTestDataLessThanMultiComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted, int32 componentIndex) +// Multi-component checks + +void CheckIntTestDataGreaterThanMultiComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted, int32 componentIndex) { const auto* thresholdArrayPtr = dataStructure.getDataAs(k_ThresholdArrayPath); REQUIRE(thresholdArrayPtr != nullptr); @@ -313,9 +332,8 @@ void CheckIntTestDataLessThanMultiComponent(const DataStructure& dataStructure, for(usize i = 0; i < k_TupleCount; i++) { - usize arrayIndex = i * k_MultiComponentCount + componentIndex; - bool value = thresholdStore[arrayIndex]; - bool expected = InputIntComponentValue(i, componentIndex) < thresholdValue; + bool value = thresholdStore[i]; + bool expected = InputIntComponentValue(i, componentIndex) > thresholdValue; if(isInverted) { @@ -326,21 +344,17 @@ void CheckIntTestDataLessThanMultiComponent(const DataStructure& dataStructure, } } -void CheckIntTestDataEqualToMultiComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted, int32 componentIndex) +void CheckIntTestDataLessThanMultiComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted, int32 componentIndex) { const auto* thresholdArrayPtr = dataStructure.getDataAs(k_ThresholdArrayPath); REQUIRE(thresholdArrayPtr != nullptr); auto& thresholdStore = thresholdArrayPtr->getDataStoreRef(); - const auto& inputStore = dataStructure.getDataAs(k_MultiComponentArrayPath)->getDataStoreRef(); - for(usize i = 0; i < k_TupleCount; i++) { - usize arrayIndex = i * k_MultiComponentCount + componentIndex; - int32 inputValue = inputStore[arrayIndex]; // store value for breakpoint testing purposes. - bool value = thresholdStore[arrayIndex]; - bool expected = InputIntComponentValue(i, componentIndex) == thresholdValue; + bool value = thresholdStore[i]; + bool expected = InputIntComponentValue(i, componentIndex) < thresholdValue; if(isInverted) { @@ -351,7 +365,7 @@ void CheckIntTestDataEqualToMultiComponent(const DataStructure& dataStructure, d } } -void CheckIntTestDataNotEqualToMultiComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted, int32 componentIndex) +void CheckIntTestDataEqualToMultiComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted, int32 componentIndex) { const auto* thresholdArrayPtr = dataStructure.getDataAs(k_ThresholdArrayPath); REQUIRE(thresholdArrayPtr != nullptr); @@ -360,9 +374,8 @@ void CheckIntTestDataNotEqualToMultiComponent(const DataStructure& dataStructure for(usize i = 0; i < k_TupleCount; i++) { - usize arrayIndex = i * k_MultiComponentCount + componentIndex; - bool value = thresholdStore[arrayIndex]; - bool expected = InputIntComponentValue(i, componentIndex) != thresholdValue; + bool value = thresholdStore[i]; + bool expected = InputIntComponentValue(i, componentIndex) == thresholdValue; if(isInverted) { @@ -373,7 +386,7 @@ void CheckIntTestDataNotEqualToMultiComponent(const DataStructure& dataStructure } } -void CheckFloatTestDataNotEqualToSingleComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted) +void CheckIntTestDataNotEqualToMultiComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted, int32 componentIndex) { const auto* thresholdArrayPtr = dataStructure.getDataAs(k_ThresholdArrayPath); REQUIRE(thresholdArrayPtr != nullptr); @@ -383,7 +396,7 @@ void CheckFloatTestDataNotEqualToSingleComponent(const DataStructure& dataStruct for(usize i = 0; i < k_TupleCount; i++) { bool value = thresholdStore[i]; - bool expected = InputFloatValue(i) != thresholdValue; + bool expected = InputIntComponentValue(i, componentIndex) != thresholdValue; if(isInverted) { @@ -422,70 +435,29 @@ TEST_CASE("SimplnxCore::MultiThresholdObjects: Valid Single Thresholds: Int", "[ DataStructure dataStructure = CreateTestDataStructure(); const DataPath targetArray = k_TestArrayIntPath; - bool isInverted = false; - - SECTION("ArrayThreshold: >") - { - const double thresholdValue = 2.0; - RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::GreaterThan, thresholdValue, isInverted); - CheckIntTestDataGreaterThanSingleComponent(dataStructure, thresholdValue, isInverted); - } - - SECTION("ArrayThreshold: <") - { - const double thresholdValue = 3.0; - RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::LessThan, thresholdValue, isInverted); - CheckIntTestDataLessThanSingleComponent(dataStructure, thresholdValue, isInverted); - } - - SECTION("ArrayThreshold: ==") - { - const double thresholdValue = 3.0; - RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::Operator_Equal, thresholdValue, isInverted); - CheckIntTestDataEqualToSingleComponent(dataStructure, thresholdValue, isInverted); - CheckIntTestDataNotEqualToSingleComponent(dataStructure, thresholdValue, !isInverted); - } - SECTION("ArrayThreshold: !=") - { - const double thresholdValue = 4.0; - RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::Operator_NotEqual, thresholdValue, isInverted); - CheckIntTestDataEqualToSingleComponent(dataStructure, thresholdValue, !isInverted); - CheckIntTestDataNotEqualToSingleComponent(dataStructure, thresholdValue, isInverted); - } -} - -TEST_CASE("SimplnxCore::MultiThresholdObjects: Valid Single Thresholds: Int Inverted", "[SimplnxCore][MultiThresholdObjectsFilter]") -{ - UnitTest::LoadPlugins(); - - DataStructure dataStructure = CreateTestDataStructure(); - const DataPath targetArray = k_TestArrayIntPath; - bool isInverted = true; + 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("ArrayThreshold: >") { - const double thresholdValue = 2.0; RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::GreaterThan, thresholdValue, isInverted); CheckIntTestDataGreaterThanSingleComponent(dataStructure, thresholdValue, isInverted); } SECTION("ArrayThreshold: <") { - const double thresholdValue = 3.0; RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::LessThan, thresholdValue, isInverted); CheckIntTestDataLessThanSingleComponent(dataStructure, thresholdValue, isInverted); } SECTION("ArrayThreshold: ==") { - const double thresholdValue = 3.0; RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::Operator_Equal, thresholdValue, isInverted); CheckIntTestDataEqualToSingleComponent(dataStructure, thresholdValue, isInverted); CheckIntTestDataNotEqualToSingleComponent(dataStructure, thresholdValue, !isInverted); } SECTION("ArrayThreshold: !=") { - const double thresholdValue = 4.0; RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::Operator_NotEqual, thresholdValue, isInverted); CheckIntTestDataEqualToSingleComponent(dataStructure, thresholdValue, !isInverted); CheckIntTestDataNotEqualToSingleComponent(dataStructure, thresholdValue, isInverted); @@ -498,72 +470,30 @@ TEST_CASE("SimplnxCore::MultiThresholdObjects: Valid Single Thresholds: Float", DataStructure dataStructure = CreateTestDataStructure(); const DataPath targetArray = k_TestArrayFloatPath; - bool isInverted = false; + double thresholdValue = GENERATE(0.0, 0.01, 0.02, 0.03, 0.04, 26.2); + bool isInverted = GENERATE(false, true); // RunSingleComponentThresholdTests(dataStructure, k_TestArrayIntPath, 3.0, false); SECTION("ArrayThreshold: >") { - const double thresholdValue = 0.04; RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::GreaterThan, thresholdValue, isInverted); CheckFloatTestDataGreaterThanSingleComponent(dataStructure, thresholdValue, isInverted); } SECTION("ArrayThreshold: <") { - const double thresholdValue = 0.02; RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::LessThan, thresholdValue, isInverted); CheckFloatTestDataLessThanSingleComponent(dataStructure, thresholdValue, isInverted); } SECTION("ArrayThreshold: ==") { - const double thresholdValue = 0.03; RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::Operator_Equal, thresholdValue, isInverted); CheckFloatTestDataEqualToSingleComponent(dataStructure, thresholdValue, isInverted); CheckFloatTestDataNotEqualToSingleComponent(dataStructure, thresholdValue, !isInverted); } SECTION("ArrayThreshold: !=") { - const double thresholdValue = 0.02; - RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::Operator_NotEqual, thresholdValue, isInverted); - CheckFloatTestDataEqualToSingleComponent(dataStructure, thresholdValue, !isInverted); - CheckFloatTestDataNotEqualToSingleComponent(dataStructure, thresholdValue, isInverted); - } -} - -TEST_CASE("SimplnxCore::MultiThresholdObjects: Valid Single Thresholds: Float Inverted", "[SimplnxCore][MultiThresholdObjectsFilter]") -{ - UnitTest::LoadPlugins(); - - DataStructure dataStructure = CreateTestDataStructure(); - const DataPath targetArray = k_TestArrayFloatPath; - bool isInverted = true; - - // RunSingleComponentThresholdTests(dataStructure, k_TestArrayIntPath, 3.0, false); - SECTION("ArrayThreshold: >") - { - const double thresholdValue = 0.02; - RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::GreaterThan, thresholdValue, isInverted); - CheckFloatTestDataGreaterThanSingleComponent(dataStructure, thresholdValue, isInverted); - } - - SECTION("ArrayThreshold: <") - { - const double thresholdValue = 0.03; - RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::LessThan, thresholdValue, isInverted); - CheckFloatTestDataLessThanSingleComponent(dataStructure, thresholdValue, isInverted); - } - - SECTION("ArrayThreshold: ==") - { - const double thresholdValue = 0.02; - RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::Operator_Equal, thresholdValue, isInverted); - CheckFloatTestDataEqualToSingleComponent(dataStructure, thresholdValue, isInverted); - CheckFloatTestDataNotEqualToSingleComponent(dataStructure, thresholdValue, !isInverted); - } - SECTION("ArrayThreshold: !=") - { - const double thresholdValue = 0.01; RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::Operator_NotEqual, thresholdValue, isInverted); CheckFloatTestDataEqualToSingleComponent(dataStructure, thresholdValue, !isInverted); CheckFloatTestDataNotEqualToSingleComponent(dataStructure, thresholdValue, isInverted); @@ -576,34 +506,31 @@ TEST_CASE("SimplnxCore::MultiThresholdObjects: Valid Single Thresholds: Int Mult DataStructure dataStructure = CreateTestDataStructure(); const DataPath targetArray = k_MultiComponentArrayPath; - bool isInverted = false; + 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); SECTION("ArrayThreshold: >") { - const double thresholdValue = 2.0; RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::GreaterThan, thresholdValue, isInverted, componentIndex); CheckIntTestDataGreaterThanMultiComponent(dataStructure, thresholdValue, isInverted, componentIndex); } SECTION("ArrayThreshold: <") { - const double thresholdValue = 3.0; - RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::LessThan, thresholdValue, isInverted); + RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::LessThan, thresholdValue, isInverted, componentIndex); CheckIntTestDataLessThanMultiComponent(dataStructure, thresholdValue, isInverted, componentIndex); } SECTION("ArrayThreshold: ==") { - const double thresholdValue = 3.0; - RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::Operator_Equal, thresholdValue, isInverted); + RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::Operator_Equal, thresholdValue, isInverted, componentIndex); CheckIntTestDataEqualToMultiComponent(dataStructure, thresholdValue, isInverted, componentIndex); CheckIntTestDataNotEqualToMultiComponent(dataStructure, thresholdValue, !isInverted, componentIndex); } SECTION("ArrayThreshold: !=") { - const double thresholdValue = 4.0; - RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::Operator_NotEqual, thresholdValue, isInverted); + RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::Operator_NotEqual, thresholdValue, isInverted, componentIndex); CheckIntTestDataEqualToMultiComponent(dataStructure, thresholdValue, !isInverted, componentIndex); CheckIntTestDataNotEqualToMultiComponent(dataStructure, thresholdValue, isInverted, componentIndex); } From 6522f0b4650229ff364e10816d3b58e03d76e9eb Mon Sep 17 00:00:00 2001 From: Matthew Marine Date: Wed, 22 Apr 2026 14:07:03 -0400 Subject: [PATCH 04/14] Removed component requirement for multiple input arrays * Removed requirement for input arrays to all have the same number of components, Each threshold specifies the target component. --- .../SimplnxCore/Filters/MultiThresholdObjectsFilter.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/MultiThresholdObjectsFilter.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/MultiThresholdObjectsFilter.cpp index 9ebe5b283d..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); From 40a94230c89e5816058cb999866bb9b390090662 Mon Sep 17 00:00:00 2001 From: Matthew Marine Date: Thu, 23 Apr 2026 09:04:31 -0400 Subject: [PATCH 05/14] Restructuring MultiThresholdObjects for Set support * MultiThresholdObjects no longer writes directly to the DataStore when running Thresholds. Instead Sets and Thresholds both store temporary vectors that are copied to the parent set's vector. The topmost ThresholdSet copies the vector to the DataStore upon completion. * Added ThresholdSet unit tests. --- .../Algorithms/MultiThresholdObjects.cpp | 66 +- .../test/MultiThresholdObjectsTest.cpp | 593 +++++++++++------- 2 files changed, 414 insertions(+), 245 deletions(-) diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/MultiThresholdObjects.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/MultiThresholdObjects.cpp index aac4fece04..8a5555aab5 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/MultiThresholdObjects.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/MultiThresholdObjects.cpp @@ -33,7 +33,7 @@ class ThresholdFilterHelper { T inputValue = m_Input.getComponentValue(tupleIndex, m_ComponentIndex); bool comparison = CompT{}(inputValue, value); - if (m_IsInverted) + if(m_IsInverted) { comparison = !comparison; } @@ -95,33 +95,33 @@ struct ExecuteThresholdHelper * @param inverse */ template -void InsertThreshold(usize numItems, AbstractDataStore& currentStore, nx::core::IArrayThreshold::UnionOperator unionOperator, std::vector& newArrayPtr, bool inverse, T trueValue, T falseValue) +void InsertThreshold(usize numItems, std::vector& currentVector, nx::core::IArrayThreshold::UnionOperator unionOperator, std::vector& newVector, 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; + newVector[i] = (newVector[i] == trueValue) ? falseValue : trueValue; } if(nx::core::IArrayThreshold::UnionOperator::Or == unionOperator) { - currentStore[i] = (currentStore[i] == trueValue || newArrayPtr[i] == trueValue) ? trueValue : falseValue; + currentVector[i] = (currentVector[i] == trueValue || newVector[i] == trueValue) ? trueValue : falseValue; } - else if(currentStore[i] == falseValue || newArrayPtr[i] == falseValue) + else if(currentVector[i] == falseValue || newVector[i] == falseValue) { - currentStore[i] = falseValue; + currentVector[i] = falseValue; } } } template -void ThresholdValue(const ArrayThreshold& comparisonValue, const DataStructure& dataStructure, AbstractDataStore& outputResultStore, int32_t& err, bool replaceInput, bool inverse, T trueValue, +void ThresholdValue(const ArrayThreshold& comparisonValue, const DataStructure& dataStructure, std::vector& outputResultVector, int32_t& err, bool replaceInput, bool inverse, 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(); + size_t totalTuples = outputResultVector.size(); std::vector tempResultVector(totalTuples, falseValue); nx::core::ArrayThreshold::ComparisonType compOperator = comparisonValue.getComparisonType(); @@ -148,13 +148,13 @@ void ThresholdValue(const ArrayThreshold& comparisonValue, const DataStructure& // copy the temp uint8 vector to the final uint8 result array for(size_t i = 0; i < totalTuples; i++) { - outputResultStore[i] = tempResultVector[i]; + outputResultVector[i] = tempResultVector[i]; } } else { // insert into current threshold - InsertThreshold(totalTuples, outputResultStore, unionOperator, tempResultVector, inverse, trueValue, falseValue); + InsertThreshold(totalTuples, outputResultVector, unionOperator, tempResultVector, inverse, trueValue, falseValue); } } @@ -165,16 +165,24 @@ struct ThresholdValueFunctor { // 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); + auto& outputDataStore = outputResultArray.template getIDataStoreRefAs>(); + usize totalTuples = outputDataStore.getNumberOfTuples(); + std::vector tmpVector(totalTuples, falseValue); + ThresholdValue(comparisonValue, dataStructure, tmpVector, err, replaceInput, inverse, trueValue, falseValue); + + for(size_t i = 0; i < totalTuples; i++) + { + outputDataStore[i] = tmpVector[i]; + } } }; template -void ThresholdSet(const ArrayThresholdSet& inputComparisonSet, const DataStructure& dataStructure, AbstractDataStore& outputResultStore, int32_t& err, bool replaceInput, bool inverse, T trueValue, +void ThresholdSet(const ArrayThresholdSet& inputComparisonSet, const DataStructure& dataStructure, std::vector& outputResultVector, int32_t& err, bool replaceInput, bool inverse, 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(); + size_t totalTuples = outputResultVector.size(); std::vector tempResultVector(totalTuples, falseValue); bool firstValueFound = false; @@ -185,16 +193,25 @@ 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, tempResultVector, err, !firstValueFound, false, 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, tempResultVector, err, !firstValueFound, false, trueValue, falseValue); firstValueFound = true; } } + // Allow ThresholdSets to be invertable + if(inputComparisonSet.isInverted()) + { + for(size_t i = 0; i < totalTuples; i++) + { + tempResultVector[i] = (tempResultVector[i] == trueValue) ? falseValue : trueValue; + } + } + if(replaceInput) { if(inverse) @@ -204,13 +221,13 @@ void ThresholdSet(const ArrayThresholdSet& inputComparisonSet, const DataStructu // copy the temp uint8 vector to the final uint8 result array for(size_t i = 0; i < totalTuples; i++) { - outputResultStore[i] = tempResultVector[i]; + outputResultVector[i] = tempResultVector[i]; } } else { // insert into current threshold - InsertThreshold(totalTuples, outputResultStore, inputComparisonSet.getUnionOperator(), tempResultVector, inverse, trueValue, falseValue); + InsertThreshold(totalTuples, outputResultVector, inputComparisonSet.getUnionOperator(), tempResultVector, inverse, trueValue, falseValue); } } @@ -222,7 +239,15 @@ struct ThresholdSetFunctor { // 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(); + std::vector tmpVector(totalTuples, falseValue); + ThresholdSet(inputComparisonSet, dataStructure, tmpVector, err, replaceInput, inverse, trueValue, falseValue); + + for(size_t i = 0; i < totalTuples; i++) + { + outputDataStore[i] = tmpVector[i]; + } } }; } // namespace @@ -258,6 +283,10 @@ Result<> MultiThresholdObjects::operator()() DataPath maskArrayPath = (*thresholdsObject.getRequiredPaths().begin()).replaceName(maskArrayName); int32_t err = 0; ArrayThresholdSet::CollectionType thresholdSet = thresholdsObject.getArrayThresholds(); + + ExecuteDataFunction(ThresholdSetFunctor{}, maskArrayType, thresholdsObject, m_DataStructure, m_DataStructure.getDataRefAs(maskArrayPath), err, !firstValueFound, + thresholdsObject.isInverted(), trueValue, falseValue); + #if 0 for(const std::shared_ptr& threshold : thresholdSet) { if(m_ShouldCancel) @@ -280,6 +309,7 @@ Result<> MultiThresholdObjects::operator()() firstValueFound = true; } } + #endif return {}; } diff --git a/src/Plugins/SimplnxCore/test/MultiThresholdObjectsTest.cpp b/src/Plugins/SimplnxCore/test/MultiThresholdObjectsTest.cpp index 25728ada0d..46d3fc8c81 100644 --- a/src/Plugins/SimplnxCore/test/MultiThresholdObjectsTest.cpp +++ b/src/Plugins/SimplnxCore/test/MultiThresholdObjectsTest.cpp @@ -93,13 +93,13 @@ DataStructure CreateTestDataStructure() } /** -* @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. -*/ + * @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; @@ -115,21 +115,15 @@ ArrayThresholdSet CreateSingleThreshold(const DataPath& arrayPath, ArrayThreshol } /** - * @brief + * @brief Runs the MultiThresholdObjects filter on the provided threshold set * @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. + * @param thresholdSet ThresholdSet to use for the MultiThresholdObjectsFilter */ -void RunSingleThresholdTest(DataStructure& dataStructure, const DataPath& arrayPath, ArrayThreshold::ComparisonType comparisonType, double value, bool isInverted, int32 componentIndex = 0) +void RunThresholdSetTest(DataStructure& dataStructure, ArrayThresholdSet thresholdSet) { MultiThresholdObjectsFilter filter; Arguments args; - auto thresholdSet = CreateSingleThreshold(arrayPath, comparisonType, value, isInverted, componentIndex); - 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)); @@ -149,71 +143,50 @@ void RunSingleThresholdTest(DataStructure& dataStructure, const DataPath& arrayP REQUIRE(thresholdArrayPtr->getNumberOfComponents() == 1); } -// Integer checks -void CheckIntTestDataGreaterThanSingleComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted) +/** + * @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) { - 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 = InputIntValue(i) > thresholdValue; - - if(isInverted) - { - expected = !expected; - } - - REQUIRE(value == expected); - } + auto thresholdSet = CreateSingleThreshold(arrayPath, comparisonType, value, isInverted, componentIndex); + RunThresholdSetTest(dataStructure, thresholdSet); } -void CheckIntTestDataLessThanSingleComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted) +// Integer checks +bool ExpectedIntSingleComponentMask(ArrayThreshold::ComparisonType comparisonType, int32 i, double thresholdValue, bool isInverted) { - const auto* thresholdArrayPtr = dataStructure.getDataAs(k_ThresholdArrayPath); - REQUIRE(thresholdArrayPtr != nullptr); + bool expected = false; - auto& thresholdStore = thresholdArrayPtr->getDataStoreRef(); - - for(usize i = 0; i < k_TupleCount; i++) + switch(comparisonType) { - bool value = thresholdStore[i]; - bool expected = InputIntValue(i) < thresholdValue; - - if(isInverted) - { - expected = !expected; - } - - REQUIRE(value == expected); + 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; } -} - -void CheckIntTestDataEqualToSingleComponent(const DataStructure& dataStructure, 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++) + if(isInverted) { - bool value = thresholdStore[i]; - bool expected = InputIntValue(i) == thresholdValue; - - if(isInverted) - { - expected = !expected; - } - - REQUIRE(value == expected); + expected = !expected; } + return expected; } -void CheckIntTestDataNotEqualToSingleComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted) +void CheckIntTestDataSingleComponent(const DataStructure& dataStructure, ArrayThreshold::ComparisonType comparisonType, double thresholdValue, bool isInverted) { const auto* thresholdArrayPtr = dataStructure.getDataAs(k_ThresholdArrayPath); REQUIRE(thresholdArrayPtr != nullptr); @@ -222,85 +195,39 @@ void CheckIntTestDataNotEqualToSingleComponent(const DataStructure& dataStructur for(usize i = 0; i < k_TupleCount; i++) { - bool value = thresholdStore[i]; - bool expected = InputIntValue(i) != thresholdValue; - - if(isInverted) - { - expected = !expected; - } - - REQUIRE(value == expected); + REQUIRE(thresholdStore[i] == ExpectedIntSingleComponentMask(comparisonType, i, thresholdValue, isInverted)); } } // Floating point checks - -void CheckFloatTestDataGreaterThanSingleComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted) +bool ExpectedFloatSingleComponentMask(ArrayThreshold::ComparisonType comparisonType, int32 i, double thresholdValue, bool isInverted) { - const auto* thresholdArrayPtr = dataStructure.getDataAs(k_ThresholdArrayPath); - REQUIRE(thresholdArrayPtr != nullptr); - - auto& thresholdStore = thresholdArrayPtr->getDataStoreRef(); + bool expected = false; - for(usize i = 0; i < k_TupleCount; i++) + switch(comparisonType) { - bool value = thresholdStore[i]; - bool expected = InputFloatValue(i) > thresholdValue; - - if(isInverted) - { - expected = !expected; - } - - REQUIRE(value == expected); + 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; } -} -void CheckFloatTestDataLessThanSingleComponent(const DataStructure& dataStructure, 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++) - { - float64 expectedValue = InputFloatValue(i); - bool value = thresholdStore[i]; - bool expected = InputFloatValue(i) < thresholdValue; - - if(isInverted) - { - expected = !expected; - } - - REQUIRE(value == expected); - } -} - -void CheckFloatTestDataEqualToSingleComponent(const DataStructure& dataStructure, 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++) + if(isInverted) { - bool value = thresholdStore[i]; - bool expected = InputFloatValue(i) == thresholdValue; - - if(isInverted) - { - expected = !expected; - } - - REQUIRE(value == expected); + expected = !expected; } + return expected; } -void CheckFloatTestDataNotEqualToSingleComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted) +void CheckFloatTestDataSingleComponent(const DataStructure& dataStructure, ArrayThreshold::ComparisonType comparisonType, double thresholdValue, bool isInverted) { const auto* thresholdArrayPtr = dataStructure.getDataAs(k_ThresholdArrayPath); REQUIRE(thresholdArrayPtr != nullptr); @@ -309,84 +236,39 @@ void CheckFloatTestDataNotEqualToSingleComponent(const DataStructure& dataStruct for(usize i = 0; i < k_TupleCount; i++) { - bool value = thresholdStore[i]; - bool expected = InputFloatValue(i) != thresholdValue; - - if(isInverted) - { - expected = !expected; - } - - REQUIRE(value == expected); + REQUIRE(thresholdStore[i] == ExpectedFloatSingleComponentMask(comparisonType, i, thresholdValue, isInverted)); } } // Multi-component checks - -void CheckIntTestDataGreaterThanMultiComponent(const DataStructure& dataStructure, 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++) - { - bool value = thresholdStore[i]; - bool expected = InputIntComponentValue(i, componentIndex) > thresholdValue; - - if(isInverted) - { - expected = !expected; - } - - REQUIRE(value == expected); - } -} - -void CheckIntTestDataLessThanMultiComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted, int32 componentIndex) +bool ExpectedIntMultiComponentMask(ArrayThreshold::ComparisonType comparisonType, int32 i, double thresholdValue, bool isInverted, int32 componentIndex) { - const auto* thresholdArrayPtr = dataStructure.getDataAs(k_ThresholdArrayPath); - REQUIRE(thresholdArrayPtr != nullptr); + bool expected = false; - auto& thresholdStore = thresholdArrayPtr->getDataStoreRef(); - - for(usize i = 0; i < k_TupleCount; i++) + switch(comparisonType) { - bool value = thresholdStore[i]; - bool expected = InputIntComponentValue(i, componentIndex) < thresholdValue; - - if(isInverted) - { - expected = !expected; - } - - REQUIRE(value == expected); + 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; } -} - -void CheckIntTestDataEqualToMultiComponent(const DataStructure& dataStructure, 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++) + if(isInverted) { - bool value = thresholdStore[i]; - bool expected = InputIntComponentValue(i, componentIndex) == thresholdValue; - - if(isInverted) - { - expected = !expected; - } - - REQUIRE(value == expected); + expected = !expected; } + return expected; } -void CheckIntTestDataNotEqualToMultiComponent(const DataStructure& dataStructure, double thresholdValue, bool isInverted, int32 componentIndex) +void CheckIntTestDataMultiComponent(const DataStructure& dataStructure, ArrayThreshold::ComparisonType comparisonType, double thresholdValue, bool isInverted, int32 componentIndex) { const auto* thresholdArrayPtr = dataStructure.getDataAs(k_ThresholdArrayPath); REQUIRE(thresholdArrayPtr != nullptr); @@ -395,15 +277,7 @@ void CheckIntTestDataNotEqualToMultiComponent(const DataStructure& dataStructure for(usize i = 0; i < k_TupleCount; i++) { - bool value = thresholdStore[i]; - bool expected = InputIntComponentValue(i, componentIndex) != thresholdValue; - - if(isInverted) - { - expected = !expected; - } - - REQUIRE(value == expected); + REQUIRE(thresholdStore[i] == ExpectedIntMultiComponentMask(comparisonType, i, thresholdValue, isInverted, componentIndex)); } } @@ -441,26 +315,26 @@ TEST_CASE("SimplnxCore::MultiThresholdObjects: Valid Single Thresholds: Int", "[ SECTION("ArrayThreshold: >") { RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::GreaterThan, thresholdValue, isInverted); - CheckIntTestDataGreaterThanSingleComponent(dataStructure, thresholdValue, isInverted); + CheckIntTestDataSingleComponent(dataStructure, ArrayThreshold::ComparisonType::GreaterThan, thresholdValue, isInverted); } SECTION("ArrayThreshold: <") { RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::LessThan, thresholdValue, isInverted); - CheckIntTestDataLessThanSingleComponent(dataStructure, thresholdValue, isInverted); + CheckIntTestDataSingleComponent(dataStructure, ArrayThreshold::ComparisonType::LessThan, thresholdValue, isInverted); } SECTION("ArrayThreshold: ==") { RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::Operator_Equal, thresholdValue, isInverted); - CheckIntTestDataEqualToSingleComponent(dataStructure, thresholdValue, isInverted); - CheckIntTestDataNotEqualToSingleComponent(dataStructure, 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); - CheckIntTestDataEqualToSingleComponent(dataStructure, thresholdValue, !isInverted); - CheckIntTestDataNotEqualToSingleComponent(dataStructure, thresholdValue, isInverted); + CheckIntTestDataSingleComponent(dataStructure, ArrayThreshold::ComparisonType::Operator_Equal, thresholdValue, !isInverted); + CheckIntTestDataSingleComponent(dataStructure, ArrayThreshold::ComparisonType::Operator_NotEqual, thresholdValue, isInverted); } } @@ -477,26 +351,26 @@ TEST_CASE("SimplnxCore::MultiThresholdObjects: Valid Single Thresholds: Float", SECTION("ArrayThreshold: >") { RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::GreaterThan, thresholdValue, isInverted); - CheckFloatTestDataGreaterThanSingleComponent(dataStructure, thresholdValue, isInverted); + CheckFloatTestDataSingleComponent(dataStructure, ArrayThreshold::ComparisonType::GreaterThan, thresholdValue, isInverted); } SECTION("ArrayThreshold: <") { RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::LessThan, thresholdValue, isInverted); - CheckFloatTestDataLessThanSingleComponent(dataStructure, thresholdValue, isInverted); + CheckFloatTestDataSingleComponent(dataStructure, ArrayThreshold::ComparisonType::LessThan, thresholdValue, isInverted); } SECTION("ArrayThreshold: ==") { RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::Operator_Equal, thresholdValue, isInverted); - CheckFloatTestDataEqualToSingleComponent(dataStructure, thresholdValue, isInverted); - CheckFloatTestDataNotEqualToSingleComponent(dataStructure, thresholdValue, !isInverted); + CheckFloatTestDataSingleComponent(dataStructure, ArrayThreshold::ComparisonType::Operator_Equal, thresholdValue, isInverted); + CheckFloatTestDataSingleComponent(dataStructure, ArrayThreshold::ComparisonType::Operator_NotEqual, thresholdValue, !isInverted); } SECTION("ArrayThreshold: !=") { RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::Operator_NotEqual, thresholdValue, isInverted); - CheckFloatTestDataEqualToSingleComponent(dataStructure, thresholdValue, !isInverted); - CheckFloatTestDataNotEqualToSingleComponent(dataStructure, thresholdValue, isInverted); + CheckFloatTestDataSingleComponent(dataStructure, ArrayThreshold::ComparisonType::Operator_Equal, thresholdValue, !isInverted); + CheckFloatTestDataSingleComponent(dataStructure, ArrayThreshold::ComparisonType::Operator_NotEqual, thresholdValue, isInverted); } } @@ -513,26 +387,291 @@ TEST_CASE("SimplnxCore::MultiThresholdObjects: Valid Single Thresholds: Int Mult SECTION("ArrayThreshold: >") { RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::GreaterThan, thresholdValue, isInverted, componentIndex); - CheckIntTestDataGreaterThanMultiComponent(dataStructure, thresholdValue, isInverted, componentIndex); + CheckIntTestDataMultiComponent(dataStructure, ArrayThreshold::ComparisonType::GreaterThan, thresholdValue, isInverted, componentIndex); } SECTION("ArrayThreshold: <") { RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::LessThan, thresholdValue, isInverted, componentIndex); - CheckIntTestDataLessThanMultiComponent(dataStructure, thresholdValue, isInverted, componentIndex); + CheckIntTestDataMultiComponent(dataStructure, ArrayThreshold::ComparisonType::LessThan, thresholdValue, isInverted, componentIndex); } SECTION("ArrayThreshold: ==") { RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::Operator_Equal, thresholdValue, isInverted, componentIndex); - CheckIntTestDataEqualToMultiComponent(dataStructure, thresholdValue, isInverted, componentIndex); - CheckIntTestDataNotEqualToMultiComponent(dataStructure, 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); - CheckIntTestDataEqualToMultiComponent(dataStructure, thresholdValue, !isInverted, componentIndex); - CheckIntTestDataNotEqualToMultiComponent(dataStructure, thresholdValue, isInverted, componentIndex); + CheckIntTestDataMultiComponent(dataStructure, ArrayThreshold::ComparisonType::Operator_Equal, thresholdValue, !isInverted, componentIndex); + CheckIntTestDataMultiComponent(dataStructure, ArrayThreshold::ComparisonType::Operator_NotEqual, thresholdValue, isInverted, componentIndex); + } +} + +/** + * @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; +} + +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; +} + +bool ExpectedThresholdSet2Mask(usize index, bool inverted) +{ + 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); + + bool expected = (expectedThreshold1 || expectedThreshold2) && expectedThreshold3; + if(inverted) + { + expected = !expected; + } + return expected; +} + +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 set1 = std::make_shared(CreateThresholdSet1()); + auto set2 = std::make_shared(CreateThresholdSet2()); + set2->setUnionOperator(ArrayThreshold::UnionOperator::Or); + + thresholdSet.setArrayThresholds({set1, set2}); + + return thresholdSet; +} + +ArrayThresholdSet CreateThresholdSet5() +{ + ArrayThresholdSet thresholdSet; + + auto set1 = std::make_shared(CreateThresholdSet1()); + auto set2 = std::make_shared(CreateThresholdSet2()); + set2->setUnionOperator(ArrayThreshold::UnionOperator::Or); + set2->setInverted(true); + + 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++) + { + 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) + { + expected = !expected; + } + + 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) + { + 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); + bool isInverted = 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); } } From 25f1986f1469cdef96025d653fb4a6afd5567fdc Mon Sep 17 00:00:00 2001 From: Matthew Marine Date: Thu, 23 Apr 2026 10:44:31 -0400 Subject: [PATCH 06/14] Fixed MultiThresholdObjects ThresholdSets algorithm * Standardized apply threshold values between thresholds and sets. * Removed unnecessary inversion parameter in threshold and set algorithm --- .../Algorithms/MultiThresholdObjects.cpp | 115 +++++------------- .../test/MultiThresholdObjectsTest.cpp | 5 +- 2 files changed, 30 insertions(+), 90 deletions(-) diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/MultiThresholdObjects.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/MultiThresholdObjects.cpp index 8a5555aab5..a0d11462be 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/MultiThresholdObjects.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/MultiThresholdObjects.cpp @@ -11,16 +11,31 @@ using namespace nx::core; namespace { +template +void ApplyThresholdValues(const IArrayThreshold& arrayThreshold, std::vector& outputResultVector, std::vector& inputThresholdVector, bool replaceInput, T trueValue, T falseValue) +{ + usize totalTuples = outputResultVector.size(); + auto unionOperator = arrayThreshold.getUnionOperator(); + bool inverse = arrayThreshold.isInverted(); + + if(replaceInput) + { + unionOperator = IArrayThreshold::UnionOperator::Or; + } + + // insert into current threshold + InsertThreshold(totalTuples, outputResultVector, unionOperator, inputThresholdVector, inverse, trueValue, falseValue); +} + template class ThresholdFilterHelper { public: - ThresholdFilterHelper(ArrayThreshold::ComparisonType compType, ArrayThreshold::ComparisonValue compValue, usize componentIndex, std::vector& output, bool isInverted) + ThresholdFilterHelper(ArrayThreshold::ComparisonType compType, ArrayThreshold::ComparisonValue compValue, usize componentIndex, std::vector& output) : m_ComparisonOperator(compType) , m_ComparisonValue(compValue) , m_ComponentIndex(componentIndex) , m_Output(output) - , m_IsInverted(isInverted) { } @@ -33,10 +48,6 @@ class ThresholdFilterHelper { T inputValue = m_Input.getComponentValue(tupleIndex, m_ComponentIndex); bool comparison = CompT{}(inputValue, value); - if(m_IsInverted) - { - comparison = !comparison; - } T outputValue = comparison ? trueValue : falseValue; m_Output[tupleIndex] = outputValue; } @@ -73,7 +84,6 @@ class ThresholdFilterHelper ArrayThreshold::ComparisonValue m_ComparisonValue; usize m_ComponentIndex = 0; std::vector& m_Output; - bool m_IsInverted = false; }; struct ExecuteThresholdHelper @@ -117,8 +127,7 @@ void InsertThreshold(usize numItems, std::vector& currentVector, nx::core::IA } template -void ThresholdValue(const ArrayThreshold& comparisonValue, const DataStructure& dataStructure, std::vector& outputResultVector, int32_t& err, bool replaceInput, bool inverse, T trueValue, - T falseValue) +void ThresholdValue(const ArrayThreshold& comparisonValue, const DataStructure& dataStructure, std::vector& 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 = outputResultVector.size(); @@ -127,35 +136,18 @@ void ThresholdValue(const ArrayThreshold& comparisonValue, const DataStructure& nx::core::ArrayThreshold::ComparisonType compOperator = comparisonValue.getComparisonType(); nx::core::ArrayThreshold::ComparisonValue compValue = comparisonValue.getComparisonValue(); nx::core::IArrayThreshold::UnionOperator unionOperator = comparisonValue.getUnionOperator(); - bool isInverted = comparisonValue.isInverted(); DataPath inputDataArrayPath = comparisonValue.getArrayPath(); usize componentIndex = comparisonValue.getComponentIndex(); - ThresholdFilterHelper helper(compOperator, compValue, componentIndex, tempResultVector, isInverted); + ThresholdFilterHelper helper(compOperator, compValue, componentIndex, tempResultVector); 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++) - { - outputResultVector[i] = tempResultVector[i]; - } - } - else - { - // insert into current threshold - InsertThreshold(totalTuples, outputResultVector, unionOperator, tempResultVector, inverse, trueValue, falseValue); - } + ApplyThresholdValues(comparisonValue, outputResultVector, tempResultVector, replaceInput, trueValue, falseValue); } struct ThresholdValueFunctor @@ -178,8 +170,7 @@ struct ThresholdValueFunctor }; template -void ThresholdSet(const ArrayThresholdSet& inputComparisonSet, const DataStructure& dataStructure, std::vector& outputResultVector, int32_t& err, bool replaceInput, bool inverse, T trueValue, - T falseValue) +void ThresholdSet(const ArrayThresholdSet& inputComparisonSet, const DataStructure& dataStructure, std::vector& 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 = outputResultVector.size(); @@ -193,56 +184,31 @@ void ThresholdSet(const ArrayThresholdSet& inputComparisonSet, const DataStructu const IArrayThreshold* thresholdPtr = threshold.get(); if(const auto* comparisonSet = dynamic_cast(thresholdPtr); comparisonSet != nullptr) { - ThresholdSet(*comparisonSet, dataStructure, tempResultVector, err, !firstValueFound, false, trueValue, falseValue); + ThresholdSet(*comparisonSet, dataStructure, tempResultVector, err, !firstValueFound, trueValue, falseValue); firstValueFound = true; } else if(const auto* comparisonValue = dynamic_cast(thresholdPtr); comparisonValue != nullptr) { - ThresholdValue(*comparisonValue, dataStructure, tempResultVector, err, !firstValueFound, false, trueValue, falseValue); + ThresholdValue(*comparisonValue, dataStructure, tempResultVector, err, !firstValueFound, trueValue, falseValue); firstValueFound = true; } } - // Allow ThresholdSets to be invertable - if(inputComparisonSet.isInverted()) - { - for(size_t i = 0; i < totalTuples; i++) - { - tempResultVector[i] = (tempResultVector[i] == trueValue) ? falseValue : trueValue; - } - } - - 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++) - { - outputResultVector[i] = tempResultVector[i]; - } - } - else - { - // insert into current threshold - InsertThreshold(totalTuples, outputResultVector, inputComparisonSet.getUnionOperator(), tempResultVector, inverse, trueValue, falseValue); - } + // Apply resulting values to output + ApplyThresholdValues(inputComparisonSet, outputResultVector, tempResultVector, 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. auto& outputDataStore = outputResultArray.template getIDataStoreRefAs>(); usize totalTuples = outputDataStore.getNumberOfTuples(); std::vector tmpVector(totalTuples, falseValue); - ThresholdSet(inputComparisonSet, dataStructure, tmpVector, err, replaceInput, inverse, trueValue, falseValue); + ThresholdSet(inputComparisonSet, dataStructure, tmpVector, err, replaceInput, trueValue, falseValue); for(size_t i = 0; i < totalTuples; i++) { @@ -284,32 +250,7 @@ Result<> MultiThresholdObjects::operator()() int32_t err = 0; ArrayThresholdSet::CollectionType thresholdSet = thresholdsObject.getArrayThresholds(); - ExecuteDataFunction(ThresholdSetFunctor{}, maskArrayType, thresholdsObject, m_DataStructure, m_DataStructure.getDataRefAs(maskArrayPath), err, !firstValueFound, - thresholdsObject.isInverted(), trueValue, falseValue); - #if 0 - 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) - { - // Do not replace values on first threshold, update firstValueFound to reflect that a threshold has been run. - 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) - { - // Do not replace values on first threshold, update firstValueFound to reflect that a threshold has been run. - ExecuteDataFunction(ThresholdValueFunctor{}, maskArrayType, *comparisonValue, m_DataStructure, m_DataStructure.getDataRefAs(maskArrayPath), err, !firstValueFound, - thresholdsObject.isInverted(), trueValue, falseValue); - firstValueFound = true; - } - } - #endif + ExecuteDataFunction(ThresholdSetFunctor{}, maskArrayType, thresholdsObject, m_DataStructure, m_DataStructure.getDataRefAs(maskArrayPath), err, !firstValueFound, trueValue, falseValue); return {}; } diff --git a/src/Plugins/SimplnxCore/test/MultiThresholdObjectsTest.cpp b/src/Plugins/SimplnxCore/test/MultiThresholdObjectsTest.cpp index 46d3fc8c81..d9d0907ca8 100644 --- a/src/Plugins/SimplnxCore/test/MultiThresholdObjectsTest.cpp +++ b/src/Plugins/SimplnxCore/test/MultiThresholdObjectsTest.cpp @@ -573,7 +573,7 @@ void CheckThresholdSet3(DataStructure& dataStructure, bool inverted) bool expectedMask2 = ExpectedThresholdSet2Mask(i, false); bool expected = expectedMask1 && expectedMask2; - if (inverted) + if(inverted) { expected = !expected; } @@ -631,8 +631,7 @@ TEST_CASE("SimplnxCore::MultiThresholdObjects: Valid Threshold Sets", "[SimplnxC UnitTest::LoadPlugins(); DataStructure dataStructure = CreateTestDataStructure(); - //bool isInverted = GENERATE(false, true); - bool isInverted = true; + bool isInverted = GENERATE(false, true); SECTION("ArraySet 1") { From 95779167f6e2460cfbc9777989a004ba3a3faf12 Mon Sep 17 00:00:00 2001 From: Matthew Marine Date: Thu, 23 Apr 2026 12:24:18 -0400 Subject: [PATCH 07/14] Re-enabled Invalid Execution unit test * Re-enabled unit test without the Mismatched components section. That case is no longer an error. --- .../test/MultiThresholdObjectsTest.cpp | 143 ++++++++---------- 1 file changed, 66 insertions(+), 77 deletions(-) diff --git a/src/Plugins/SimplnxCore/test/MultiThresholdObjectsTest.cpp b/src/Plugins/SimplnxCore/test/MultiThresholdObjectsTest.cpp index d9d0907ca8..ba2c11a384 100644 --- a/src/Plugins/SimplnxCore/test/MultiThresholdObjectsTest.cpp +++ b/src/Plugins/SimplnxCore/test/MultiThresholdObjectsTest.cpp @@ -674,6 +674,72 @@ TEST_CASE("SimplnxCore::MultiThresholdObjects: Valid Threshold Sets", "[SimplnxC } } +// Invalid executions + +TEST_CASE("SimplnxCore::MultiThresholdObjects: Invalid Execution", "[SimplnxCore][MultiThresholdObjectsFilter]") +{ + UnitTest::LoadPlugins(); + + MultiThresholdObjectsFilter filter; + DataStructure dataStructure = CreateTestDataStructure(); + Arguments args; + args.insertOrAssign(MultiThresholdObjectsFilter::k_CreatedDataName_Key, std::make_any(k_ThresholdArrayName)); + + SECTION("Empty ArrayThresholdSet") + { + ArrayThresholdSet thresholdSet; + + args.insertOrAssign(MultiThresholdObjectsFilter::k_ArrayThresholdsObject_Key, std::make_any(thresholdSet)); + } + SECTION("Empty ArrayThreshold DataPath") + { + ArrayThresholdSet thresholdSet; + auto threshold = std::make_shared(); + threshold->setComparisonType(ArrayThreshold::ComparisonType::GreaterThan); + threshold->setComparisonValue(0.1); + thresholdSet.setArrayThresholds({threshold}); + + args.insertOrAssign(MultiThresholdObjectsFilter::k_ArrayThresholdsObject_Key, std::make_any(thresholdSet)); + } + SECTION("Out of Bounds Component Index") + { + ArrayThresholdSet thresholdSet; + auto threshold = std::make_shared(); + threshold->setArrayPath(k_TestArrayFloatPath); + threshold->setComparisonType(ArrayThreshold::ComparisonType::GreaterThan); + threshold->setComparisonValue(0.1); + threshold->setComponentIndex(1); + thresholdSet.setArrayThresholds({threshold}); + + args.insertOrAssign(MultiThresholdObjectsFilter::k_ArrayThresholdsObject_Key, std::make_any(thresholdSet)); + } + SECTION("Mismatching Tuples 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_MismatchingTuplesArrayPath); + threshold2->setComparisonType(ArrayThreshold::ComparisonType::GreaterThan); + threshold2->setComparisonValue(0.1); + thresholdSet.setArrayThresholds({threshold1, threshold2}); + + args.insertOrAssign(MultiThresholdObjectsFilter::k_ArrayThresholdsObject_Key, std::make_any(thresholdSet)); + } + + // Preflight the filter and check result + auto preflightResult = filter.preflight(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_INVALID(preflightResult.outputActions) + + // Execute the filter and check the result + auto executeResult = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_INVALID(executeResult.result) + + UnitTest::CheckArraysInheritTupleDims(dataStructure); +} + /// /// /////// /// @@ -822,84 +888,7 @@ TEMPLATE_TEST_CASE("SimplnxCore::MultiThresholdObjects: Valid Execution - Custom } } -TEST_CASE("SimplnxCore::MultiThresholdObjects: Invalid Execution", "[SimplnxCore][MultiThresholdObjectsFilter]") -{ - UnitTest::LoadPlugins(); - - MultiThresholdObjectsFilter filter; - DataStructure dataStructure = CreateTestDataStructure(); - Arguments args; - args.insertOrAssign(MultiThresholdObjectsFilter::k_CreatedDataName_Key, std::make_any(k_ThresholdArrayName)); - - SECTION("Empty ArrayThresholdSet") - { - ArrayThresholdSet thresholdSet; - - args.insertOrAssign(MultiThresholdObjectsFilter::k_ArrayThresholdsObject_Key, std::make_any(thresholdSet)); - } - SECTION("Empty ArrayThreshold DataPath") - { - ArrayThresholdSet thresholdSet; - auto threshold = std::make_shared(); - threshold->setComparisonType(ArrayThreshold::ComparisonType::GreaterThan); - threshold->setComparisonValue(0.1); - thresholdSet.setArrayThresholds({threshold}); - - 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; - auto threshold = std::make_shared(); - threshold->setArrayPath(k_TestArrayFloatPath); - threshold->setComparisonType(ArrayThreshold::ComparisonType::GreaterThan); - threshold->setComparisonValue(0.1); - threshold->setComponentIndex(1); - thresholdSet.setArrayThresholds({threshold}); - - args.insertOrAssign(MultiThresholdObjectsFilter::k_ArrayThresholdsObject_Key, std::make_any(thresholdSet)); - } - SECTION("Mismatching Tuples 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_MismatchingTuplesArrayPath); - threshold2->setComparisonType(ArrayThreshold::ComparisonType::GreaterThan); - threshold2->setComparisonValue(0.1); - thresholdSet.setArrayThresholds({threshold1, threshold2}); - args.insertOrAssign(MultiThresholdObjectsFilter::k_ArrayThresholdsObject_Key, std::make_any(thresholdSet)); - } - - // Preflight the filter and check result - auto preflightResult = filter.preflight(dataStructure, args); - SIMPLNX_RESULT_REQUIRE_INVALID(preflightResult.outputActions) - - // Execute the filter and check the result - auto executeResult = filter.execute(dataStructure, args); - SIMPLNX_RESULT_REQUIRE_INVALID(executeResult.result) - - UnitTest::CheckArraysInheritTupleDims(dataStructure); -} TEMPLATE_TEST_CASE("SimplnxCore::MultiThresholdObjects: Invalid Execution - Out of Bounds Custom Values", "[SimplnxCore][MultiThresholdObjectsFilter]", int8, uint8, int16, uint16, int32, uint32, int64, uint64, float32) From debe9001e06b8f64e8e886fd5da1492f74f74e5f Mon Sep 17 00:00:00 2001 From: Matthew Marine Date: Thu, 23 Apr 2026 12:49:53 -0400 Subject: [PATCH 08/14] Cleaned up MultiThresholdObjects algorithm * Added function documentation for ApplyThresholdValues * Simplified InsertThreshold parameters. * Deleted unused ThresholdValueFunctor struct. --- .../Algorithms/MultiThresholdObjects.cpp | 35 +++++++------------ 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/MultiThresholdObjects.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/MultiThresholdObjects.cpp index a0d11462be..695621ea9d 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/MultiThresholdObjects.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/MultiThresholdObjects.cpp @@ -11,10 +11,18 @@ using namespace nx::core; namespace { +/** + * @brief Consolidate all assignment calls to a single method to prevent unintended diverging behavior. + * @param arrayThreshold Current threshold to pull settings from. + * @param outputResultVector Output vector for the current ThresholdSet. + * @param inputThresholdVector 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, std::vector& outputResultVector, std::vector& inputThresholdVector, bool replaceInput, T trueValue, T falseValue) { - usize totalTuples = outputResultVector.size(); auto unionOperator = arrayThreshold.getUnionOperator(); bool inverse = arrayThreshold.isInverted(); @@ -24,7 +32,7 @@ void ApplyThresholdValues(const IArrayThreshold& arrayThreshold, std::vector& } // insert into current threshold - InsertThreshold(totalTuples, outputResultVector, unionOperator, inputThresholdVector, inverse, trueValue, falseValue); + InsertThreshold(outputResultVector, unionOperator, inputThresholdVector, inverse, trueValue, falseValue); } template @@ -105,8 +113,10 @@ struct ExecuteThresholdHelper * @param inverse */ template -void InsertThreshold(usize numItems, std::vector& currentVector, nx::core::IArrayThreshold::UnionOperator unionOperator, std::vector& newVector, bool inverse, T trueValue, T falseValue) +void InsertThreshold(std::vector& currentVector, nx::core::IArrayThreshold::UnionOperator unionOperator, std::vector& newVector, bool inverse, T trueValue, T falseValue) { + usize numItems = currentVector.size(); + for(usize i = 0; i < numItems; i++) { // invert the current comparison if necessary @@ -150,25 +160,6 @@ void ThresholdValue(const ArrayThreshold& comparisonValue, const DataStructure& ApplyThresholdValues(comparisonValue, outputResultVector, tempResultVector, 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. - auto& outputDataStore = outputResultArray.template getIDataStoreRefAs>(); - usize totalTuples = outputDataStore.getNumberOfTuples(); - std::vector tmpVector(totalTuples, falseValue); - ThresholdValue(comparisonValue, dataStructure, tmpVector, err, replaceInput, inverse, trueValue, falseValue); - - for(size_t i = 0; i < totalTuples; i++) - { - outputDataStore[i] = tmpVector[i]; - } - } -}; - template void ThresholdSet(const ArrayThresholdSet& inputComparisonSet, const DataStructure& dataStructure, std::vector& outputResultVector, int32_t& err, bool replaceInput, T trueValue, T falseValue) { From 61ff8d51cd2f2eb57c0a6e93ed0201b68a39a977 Mon Sep 17 00:00:00 2001 From: Matthew Marine Date: Thu, 23 Apr 2026 12:59:47 -0400 Subject: [PATCH 09/14] Re-enabled. updated, and cleaned up unit tests * Re-enabled invalid execution and mask DataType unit tests and updated for new tuple counts. * Simplified mask DataType unit tests to remove duplicated code. * Removed unused legacy unit tests. --- .../test/MultiThresholdObjectsTest.cpp | 506 ++---------------- 1 file changed, 48 insertions(+), 458 deletions(-) diff --git a/src/Plugins/SimplnxCore/test/MultiThresholdObjectsTest.cpp b/src/Plugins/SimplnxCore/test/MultiThresholdObjectsTest.cpp index ba2c11a384..fb32bfd070 100644 --- a/src/Plugins/SimplnxCore/test/MultiThresholdObjectsTest.cpp +++ b/src/Plugins/SimplnxCore/test/MultiThresholdObjectsTest.cpp @@ -347,7 +347,6 @@ TEST_CASE("SimplnxCore::MultiThresholdObjects: Valid Single Thresholds: Float", double thresholdValue = GENERATE(0.0, 0.01, 0.02, 0.03, 0.04, 26.2); bool isInverted = GENERATE(false, true); - // RunSingleComponentThresholdTests(dataStructure, k_TestArrayIntPath, 3.0, false); SECTION("ArrayThreshold: >") { RunSingleThresholdTest(dataStructure, targetArray, ArrayThreshold::ComparisonType::GreaterThan, thresholdValue, isInverted); @@ -740,158 +739,8 @@ TEST_CASE("SimplnxCore::MultiThresholdObjects: Invalid Execution", "[SimplnxCore UnitTest::CheckArraysInheritTupleDims(dataStructure); } -/// -/// /////// -/// - -#if false -TEST_CASE("SimplnxCore::MultiThresholdObjects: Valid Execution", "[SimplnxCore][MultiThresholdObjectsFilter]") -{ - UnitTest::LoadPlugins(); - - DataStructure dataStructure = CreateTestDataStructure(); - - SECTION("Float Array 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::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); - - // 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("Int Array Threshold") - { - MultiThresholdObjectsFilter filter; - Arguments args; - - 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_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); - - // 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); - } - } - } - - UnitTest::CheckArraysInheritTupleDims(dataStructure); -} - -TEMPLATE_TEST_CASE("SimplnxCore::MultiThresholdObjects: Valid Execution - Custom Values", "[SimplnxCore][MultiThresholdObjectsFilter]", int8, uint8, int16, uint16, int32, uint32, int64, uint64, - float32, float64) -{ - UnitTest::LoadPlugins(); - - MultiThresholdObjectsFilter filter; - DataStructure dataStructure = CreateTestDataStructure(); - Arguments args; - - float64 trueValue = 25; - float64 falseValue = 10; - - 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())); - - // 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); - - // 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] == falseValue); - } - else - { - REQUIRE((*thresholdArray)[i] == trueValue); - } - } -} - - - -TEMPLATE_TEST_CASE("SimplnxCore::MultiThresholdObjects: Invalid Execution - Out of Bounds Custom Values", "[SimplnxCore][MultiThresholdObjectsFilter]", int8, uint8, int16, uint16, int32, uint32, - int64, uint64, float32) +TEMPLATE_TEST_CASE("SimplnxCore::MultiThresholdObjects: Invalid Execution - Out of Bounds Custom Values", "[SimplnxCore][MultiThresholdObjectsFilter]", int8, uint8, int16, uint16, int32, uint32, int64, + uint64, float32) { UnitTest::LoadPlugins(); @@ -995,395 +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); } -#endif - -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"); - } - } -} From 08fd31140f61444ce8bae78c62d9a8f2d6765f1d Mon Sep 17 00:00:00 2001 From: Matthew Marine Date: Fri, 24 Apr 2026 10:16:43 -0400 Subject: [PATCH 10/14] Clang format unit test file --- src/Plugins/SimplnxCore/test/MultiThresholdObjectsTest.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Plugins/SimplnxCore/test/MultiThresholdObjectsTest.cpp b/src/Plugins/SimplnxCore/test/MultiThresholdObjectsTest.cpp index fb32bfd070..cb0fd4dc1b 100644 --- a/src/Plugins/SimplnxCore/test/MultiThresholdObjectsTest.cpp +++ b/src/Plugins/SimplnxCore/test/MultiThresholdObjectsTest.cpp @@ -739,8 +739,8 @@ TEST_CASE("SimplnxCore::MultiThresholdObjects: Invalid Execution", "[SimplnxCore UnitTest::CheckArraysInheritTupleDims(dataStructure); } -TEMPLATE_TEST_CASE("SimplnxCore::MultiThresholdObjects: Invalid Execution - Out of Bounds Custom Values", "[SimplnxCore][MultiThresholdObjectsFilter]", int8, uint8, int16, uint16, int32, uint32, int64, - uint64, float32) +TEMPLATE_TEST_CASE("SimplnxCore::MultiThresholdObjects: Invalid Execution - Out of Bounds Custom Values", "[SimplnxCore][MultiThresholdObjectsFilter]", int8, uint8, int16, uint16, int32, uint32, + int64, uint64, float32) { UnitTest::LoadPlugins(); From eedeee40a9767834191bc1a5804128b7223914cb Mon Sep 17 00:00:00 2001 From: Matthew Marine Date: Fri, 24 Apr 2026 10:37:08 -0400 Subject: [PATCH 11/14] Fixed macOS / Linux compile error --- .../Algorithms/MultiThresholdObjects.cpp | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/MultiThresholdObjects.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/MultiThresholdObjects.cpp index 695621ea9d..d539154123 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/MultiThresholdObjects.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/MultiThresholdObjects.cpp @@ -11,6 +11,38 @@ using namespace nx::core; namespace { +/** + * @brief InsertThreshold + * @param numItems + * @param currentArrayPtr + * @param unionOperator + * @param newArrayPtr + * @param inverse + */ +template +void InsertThreshold(std::vector& currentVector, nx::core::IArrayThreshold::UnionOperator unionOperator, std::vector& newVector, bool inverse, T trueValue, T falseValue) +{ + usize numItems = currentVector.size(); + + 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. @@ -104,38 +136,6 @@ struct ExecuteThresholdHelper } }; -/** - * @brief InsertThreshold - * @param numItems - * @param currentArrayPtr - * @param unionOperator - * @param newArrayPtr - * @param inverse - */ -template -void InsertThreshold(std::vector& currentVector, nx::core::IArrayThreshold::UnionOperator unionOperator, std::vector& newVector, bool inverse, T trueValue, T falseValue) -{ - usize numItems = currentVector.size(); - - 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; - } - } -} - template void ThresholdValue(const ArrayThreshold& comparisonValue, const DataStructure& dataStructure, std::vector& outputResultVector, int32_t& err, bool replaceInput, T trueValue, T falseValue) { From a5ceabf2cf4dda89e054526d1bca6f2a3b59d935 Mon Sep 17 00:00:00 2001 From: Matthew Marine Date: Tue, 28 Apr 2026 13:19:33 -0400 Subject: [PATCH 12/14] Updated for large data * Converted std::vector data to AbstractDataStore using DataStoreUtilities. --- .../Algorithms/MultiThresholdObjects.cpp | 51 +++++++++++-------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/MultiThresholdObjects.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/MultiThresholdObjects.cpp index d539154123..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 @@ -20,9 +21,9 @@ namespace * @param inverse */ template -void InsertThreshold(std::vector& currentVector, nx::core::IArrayThreshold::UnionOperator unionOperator, std::vector& newVector, bool inverse, T trueValue, T falseValue) +void InsertThreshold(AbstractDataStore& currentVector, nx::core::IArrayThreshold::UnionOperator unionOperator, AbstractDataStore& newVector, bool inverse, T trueValue, T falseValue) { - usize numItems = currentVector.size(); + usize numItems = currentVector.getNumberOfTuples(); for(usize i = 0; i < numItems; i++) { @@ -46,14 +47,14 @@ void InsertThreshold(std::vector& currentVector, nx::core::IArrayThreshold::U /** * @brief Consolidate all assignment calls to a single method to prevent unintended diverging behavior. * @param arrayThreshold Current threshold to pull settings from. - * @param outputResultVector Output vector for the current ThresholdSet. - * @param inputThresholdVector Resulting output for the target array threshold. + * @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, std::vector& outputResultVector, std::vector& inputThresholdVector, bool replaceInput, T trueValue, T falseValue) +void ApplyThresholdValues(const IArrayThreshold& arrayThreshold, AbstractDataStore& outputResultStore, AbstractDataStore& inputThresholdStore, bool replaceInput, T trueValue, T falseValue) { auto unionOperator = arrayThreshold.getUnionOperator(); bool inverse = arrayThreshold.isInverted(); @@ -64,14 +65,14 @@ void ApplyThresholdValues(const IArrayThreshold& arrayThreshold, std::vector& } // insert into current threshold - InsertThreshold(outputResultVector, unionOperator, inputThresholdVector, inverse, trueValue, falseValue); + 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) @@ -123,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 @@ -137,11 +138,13 @@ struct ExecuteThresholdHelper }; template -void ThresholdValue(const ArrayThreshold& comparisonValue, const DataStructure& dataStructure, std::vector& outputResultVector, int32_t& err, bool replaceInput, 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 = outputResultVector.size(); - 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(); @@ -151,21 +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); - ApplyThresholdValues(comparisonValue, outputResultVector, tempResultVector, replaceInput, trueValue, falseValue); + ApplyThresholdValues(comparisonValue, outputResultVector, tempResultStore, replaceInput, trueValue, falseValue); } template -void ThresholdSet(const ArrayThresholdSet& inputComparisonSet, const DataStructure& dataStructure, std::vector& outputResultVector, int32_t& err, bool replaceInput, 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 = outputResultVector.size(); - 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; @@ -175,18 +180,18 @@ void ThresholdSet(const ArrayThresholdSet& inputComparisonSet, const DataStructu const IArrayThreshold* thresholdPtr = threshold.get(); if(const auto* comparisonSet = dynamic_cast(thresholdPtr); comparisonSet != nullptr) { - ThresholdSet(*comparisonSet, dataStructure, tempResultVector, err, !firstValueFound, 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, tempResultVector, err, !firstValueFound, trueValue, falseValue); + ThresholdValue(*comparisonValue, dataStructure, tempResultStore, err, !firstValueFound, trueValue, falseValue); firstValueFound = true; } } // Apply resulting values to output - ApplyThresholdValues(inputComparisonSet, outputResultVector, tempResultVector, replaceInput, trueValue, falseValue); + ApplyThresholdValues(inputComparisonSet, outputResultVector, tempResultStore, replaceInput, trueValue, falseValue); } struct ThresholdSetFunctor @@ -198,12 +203,14 @@ struct ThresholdSetFunctor // was essentially done in the preflight part. auto& outputDataStore = outputResultArray.template getIDataStoreRefAs>(); usize totalTuples = outputDataStore.getNumberOfTuples(); - std::vector tmpVector(totalTuples, falseValue); - ThresholdSet(inputComparisonSet, dataStructure, tmpVector, err, replaceInput, trueValue, falseValue); + 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] = tmpVector[i]; + outputDataStore[i] = tempResultStore[i]; } } }; From d18b0f34def4dba1b8ede2cb58e0c63a2b5e95bb Mon Sep 17 00:00:00 2001 From: Matthew Marine Date: Thu, 23 Jul 2026 19:17:12 -0400 Subject: [PATCH 13/14] Add input array DataType coverage tests --- .../test/MultiThresholdObjectsTest.cpp | 166 +++++++++++++++++- 1 file changed, 162 insertions(+), 4 deletions(-) diff --git a/src/Plugins/SimplnxCore/test/MultiThresholdObjectsTest.cpp b/src/Plugins/SimplnxCore/test/MultiThresholdObjectsTest.cpp index cb0fd4dc1b..2a209c1e35 100644 --- a/src/Plugins/SimplnxCore/test/MultiThresholdObjectsTest.cpp +++ b/src/Plugins/SimplnxCore/test/MultiThresholdObjectsTest.cpp @@ -69,10 +69,10 @@ DataStructure CreateTestDataStructure() Int32Array* data1 = Int32Array::CreateWithStore(dataStructure, k_TestArrayIntName, tDims, cDims, am->getId()); Int32Array* multiComponentData = Int32Array::CreateWithStore(dataStructure, k_MultiComponentArrayName, tDims, cDimsMulti, am->getId()); - Float32Array* invalid1 = Float32Array::CreateWithStore(dataStructure, k_MismatchingComponentsArrayPath.getTargetName(), tDims, cDimsMulti, am->getId()); - invalid1->fill(1.0); - Float32Array* invalid2 = Float32Array::CreateWithStore(dataStructure, k_MismatchingTuplesArrayPath.getTargetName(), std::vector{10}, cDims); - invalid2->fill(2.0); + // Mismatched tuple count throws an error. + // This is not true for mismatched component shapes. + Float32Array* problemArray = Float32Array::CreateWithStore(dataStructure, k_MismatchingTuplesArrayPath.getTargetName(), std::vector{10}, cDims); + problemArray->fill(2.0); usize numComponents = multiComponentData->getNumberOfComponents(); @@ -92,6 +92,58 @@ DataStructure CreateTestDataStructure() return dataStructure; } +template +void SetArrayValues(DataArray& dataArray) +{ + auto& dataStore = dataArray.getDataStoreRef(); + usize count = dataStore.size(); + for(usize i = 0; i < count; i++) + { + dataStore[i] = static_cast(i); + } +} + +DataStructure CreateTestDataStructure2() +{ + 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 = {k_TupleCount, 1, 1}; + image->setDimensions(dims); + + ShapeType tDims = {k_TupleCount}; + ShapeType cDims = {1}; + ShapeType cDimsMulti = {k_MultiComponentCount}; + + AttributeMatrix* am = AttributeMatrix::Create(dataStructure, k_CellData, tDims, image->getId()); + auto* int8Array = Int8Array::CreateWithStore(dataStructure, "int8", tDims, cDims, am->getId()); + auto* int16Array = Int16Array::CreateWithStore(dataStructure, "int16", tDims, cDims, am->getId()); + auto* int32Array = Int32Array::CreateWithStore(dataStructure, "int32", tDims, cDims, am->getId()); + auto* int64Array = Int64Array::CreateWithStore(dataStructure, "int64", tDims, cDims, am->getId()); + auto* uint8Array = UInt8Array::CreateWithStore(dataStructure, "uint8", tDims, cDims, am->getId()); + auto* uint16Array = UInt16Array::CreateWithStore(dataStructure, "uint16", tDims, cDims, am->getId()); + auto* uint32Array = UInt32Array::CreateWithStore(dataStructure, "uint32", tDims, cDims, am->getId()); + auto* uint64Array = UInt64Array::CreateWithStore(dataStructure, "uint64", tDims, cDims, am->getId()); + auto* float32Array = Float32Array::CreateWithStore(dataStructure, "float32", tDims, cDims, am->getId()); + auto* float64Array = Float64Array::CreateWithStore(dataStructure, "float64", tDims, cDims, am->getId()); + auto* boolArray = BoolArray::CreateWithStore(dataStructure, "bool", tDims, cDims, am->getId()); + + SetArrayValues(*int8Array); + SetArrayValues(*int16Array); + SetArrayValues(*int32Array); + SetArrayValues(*int64Array); + SetArrayValues(*uint8Array); + SetArrayValues(*uint16Array); + SetArrayValues(*uint32Array); + SetArrayValues(*uint64Array); + SetArrayValues(*float32Array); + SetArrayValues(*float64Array); + SetArrayValues(*boolArray); + + return dataStructure; +} + /** * @brief Creates a single threshold for the filter to use. * @param arrayPath Input DataArray path @@ -977,3 +1029,109 @@ TEST_CASE("SimplnxCore::MultiThresholdObjects: Valid Execution, Mask DataType", UnitTest::CheckArraysInheritTupleDims(dataStructure); } + +void TestMaskOutputForInputType(Int8AbstractDataStore& mask, float64 comparisonValue) +{ + usize count = mask.size(); + for(usize i = 0; i < count; i++) + { + int8 targetValue = (i < comparisonValue) ? 1 : 0; + REQUIRE(mask[i] == targetValue); + } +} + +TEST_CASE("SimplnxCore::MultiThresholdObjects: Valid Execution, Input Array DataType", "[SimplnxCore][MultiThresholdObjectsFilter]") +{ + UnitTest::LoadPlugins(); + + DataStructure dataStructure = CreateTestDataStructure2(); + + float64 comparisonValue = 3.0; + DataPath matrixPath({k_ImageGeometry, k_CellData}); + + // Shared filter setup + MultiThresholdObjectsFilter filter; + Arguments args; + + ArrayThresholdSet thresholdSet; + auto threshold = std::make_shared(); + threshold->setComparisonType(ArrayThreshold::ComparisonType::LessThan); + threshold->setComparisonValue(comparisonValue); + thresholdSet.setArrayThresholds({threshold}); + + // Signed + SECTION("Int8") + { + threshold->setArrayPath(matrixPath.createChildPath("int8")); + args.insertOrAssign(MultiThresholdObjectsFilter::k_ArrayThresholdsObject_Key, std::make_any(thresholdSet)); + } + SECTION("Int16") + { + threshold->setArrayPath(matrixPath.createChildPath("int16")); + args.insertOrAssign(MultiThresholdObjectsFilter::k_ArrayThresholdsObject_Key, std::make_any(thresholdSet)); + } + SECTION("Int32") + { + threshold->setArrayPath(matrixPath.createChildPath("int32")); + args.insertOrAssign(MultiThresholdObjectsFilter::k_ArrayThresholdsObject_Key, std::make_any(thresholdSet)); + } + SECTION("Int64") + { + threshold->setArrayPath(matrixPath.createChildPath("int64")); + args.insertOrAssign(MultiThresholdObjectsFilter::k_ArrayThresholdsObject_Key, std::make_any(thresholdSet)); + } + // Unsigned + SECTION("UInt8") + { + threshold->setArrayPath(matrixPath.createChildPath("uint8")); + args.insertOrAssign(MultiThresholdObjectsFilter::k_ArrayThresholdsObject_Key, std::make_any(thresholdSet)); + } + SECTION("UInt16") + { + threshold->setArrayPath(matrixPath.createChildPath("uint16")); + args.insertOrAssign(MultiThresholdObjectsFilter::k_ArrayThresholdsObject_Key, std::make_any(thresholdSet)); + } + SECTION("UInt32") + { + threshold->setArrayPath(matrixPath.createChildPath("uint32")); + args.insertOrAssign(MultiThresholdObjectsFilter::k_ArrayThresholdsObject_Key, std::make_any(thresholdSet)); + } + SECTION("UInt64") + { + threshold->setArrayPath(matrixPath.createChildPath("uint64")); + args.insertOrAssign(MultiThresholdObjectsFilter::k_ArrayThresholdsObject_Key, std::make_any(thresholdSet)); + } + // Floating Point + SECTION("Float32") + { + threshold->setArrayPath(matrixPath.createChildPath("float32")); + args.insertOrAssign(MultiThresholdObjectsFilter::k_ArrayThresholdsObject_Key, std::make_any(thresholdSet)); + } + SECTION("Float64") + { + threshold->setArrayPath(matrixPath.createChildPath("float64")); + args.insertOrAssign(MultiThresholdObjectsFilter::k_ArrayThresholdsObject_Key, std::make_any(thresholdSet)); + } + // Bool + SECTION("Boolean") + { + threshold->setArrayPath(matrixPath.createChildPath("bool")); + args.insertOrAssign(MultiThresholdObjectsFilter::k_ArrayThresholdsObject_Key, std::make_any(thresholdSet)); + comparisonValue = 0.9; + } + + 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) + + auto* maskArray = dataStructure.getDataAs(matrixPath.createChildPath(k_ThresholdArrayName)); + auto& maskStore = maskArray->getDataStoreRef(); + TestMaskOutputForInputType(maskStore, comparisonValue); +} From 909bff1180acae8af0ada92657899e1892f4fb98 Mon Sep 17 00:00:00 2001 From: Matthew Marine Date: Thu, 23 Jul 2026 19:59:39 -0400 Subject: [PATCH 14/14] Add: V&V docs Removed stale testing constant. --- .../test/MultiThresholdObjectsTest.cpp | 1 - .../vv/MultiThresholdObjectsFilter.md | 131 ++++++++++++++++++ .../deviations/MultiThresholdObjectsFilter.md | 41 ++++++ 3 files changed, 172 insertions(+), 1 deletion(-) create mode 100644 src/Plugins/SimplnxCore/vv/MultiThresholdObjectsFilter.md create mode 100644 src/Plugins/SimplnxCore/vv/deviations/MultiThresholdObjectsFilter.md diff --git a/src/Plugins/SimplnxCore/test/MultiThresholdObjectsTest.cpp b/src/Plugins/SimplnxCore/test/MultiThresholdObjectsTest.cpp index 2a209c1e35..bc54c0481f 100644 --- a/src/Plugins/SimplnxCore/test/MultiThresholdObjectsTest.cpp +++ b/src/Plugins/SimplnxCore/test/MultiThresholdObjectsTest.cpp @@ -28,7 +28,6 @@ const DataPath k_TestArrayIntPath = k_ImageCellDataName.createChildPath(k_TestAr const DataPath k_MultiComponentArrayPath = k_ImageCellDataName.createChildPath(k_MultiComponentArrayName); const DataPath k_ThresholdArrayPath = k_ImageCellDataName.createChildPath(k_ThresholdArrayName); -const DataPath k_MismatchingComponentsArrayPath = k_ImageCellDataName.createChildPath("MismatchingComponentsArray"); const DataPath k_MismatchingTuplesArrayPath({"MismatchingTuplesArray"}); constexpr int8 k_TupleCount = 5; diff --git a/src/Plugins/SimplnxCore/vv/MultiThresholdObjectsFilter.md b/src/Plugins/SimplnxCore/vv/MultiThresholdObjectsFilter.md new file mode 100644 index 0000000000..0b9577ecd8 --- /dev/null +++ b/src/Plugins/SimplnxCore/vv/MultiThresholdObjectsFilter.md @@ -0,0 +1,131 @@ +# V&V Report: MultiThresholdObjectsFilter + +| | | +|--------|--------------| +| Plugin | SimplnxCore | +| SIMPLNX UUID | `4246245e-1011-4add-8436-0af6bed19228` | +| DREAM3D 6.5.171 equivalent | Two separate legacy filters, consolidated: **Threshold Objects** (`MultiThresholdObjects`, SIMPL UUID `014b7300-cf36-5ede-a751-5faf9b119dae`) and **Threshold Objects (Advanced)** (`MultiThresholdObjects2`, SIMPL UUID `686d5393-2b02-5c86-b887-dd81a8ae80f2`) — both mapped to this single filter's UUID in `SimplnxCoreLegacyUUIDMapping.hpp` (see Algorithm Relationship) | +| Verified commit | ** | +| Status | READY FOR REVIEW | +| Sign-off | *pending — DRAFT, not yet reviewed* | + +## At a glance + +| Aspect | Current state | +|------------------------|------------------------------------------------------------------------------------------------------------------------------| +| Algorithm Relationship | **Rewrite.** Consolidates two independently-shipped legacy filters — **Threshold Objects** (flat, AND-only) and **Threshold Objects (Advanced)** (nested AND/OR sets) — into one SIMPLNX filter under one new UUID, unified around a single `ArrayThresholdSet` model. Not a line-by-line translation of either legacy source. | +| Oracle (confirmed) | **Class 1 (Analytical) — confirmed.** `expected[i] = COMPARISON(input[i], value)`, hand-combined via AND/OR/invert boolean algebra. Encoded as 9 `TEST_CASE` groups (17 ctest entries) in `MultiThresholdObjectsTest.cpp`, all pass. | +| Code paths enumerated | **23 of 24 exercised.** Only the unreachable comparison-operator `else`-throw is untested (all four `ComparisonType` enumerators are covered elsewhere). | +| Tests today | **9 `TEST_CASE` groups / 17 ctest entries.** Exhaustive sweeps over comparison operator × invert × union operator × set nesting × mask `DataType` (11 types) × source-array `DataType` (11 types), plus 4 negative/error-path groups. All fixtures built in-memory. | +| Exemplar archive | **None.** All fixtures are constructed in-memory by `CreateTestDataStructure()` / `CreateTestDataStructure2()`; no `.dream3d` exemplar or `download_test_data()` entry exists for this filter. | +| Legacy comparison | **Not run** — no local build of legacy DREAM3D 6.5.171 (or its Advanced comparison-set variant) is available in this environment. Deferred; see `vv/deviations/MultiThresholdObjectsFilter.md`. | +| Bug flags | None identified. Provisional — no legacy comparison has been run yet. | +| V&V phase | Oracle chosen and applied (Class 1), code paths enumerated (23/24), test inventory documented. **Outstanding:** legacy 6.5.171 runtime A/B comparison, second-engineer oracle review, status promotion DRAFT → READY FOR REVIEW. | + +For worked instances see `src/Plugins/OrientationAnalysis/vv/BadDataNeighborOrientationCheckFilter.md` and `src/Plugins/OrientationAnalysis/vv/ComputeAvgCAxesFilter.md` (on `topic/vv/compute_avg_caxis`). + +## Summary + +`MultiThresholdObjectsFilter` builds a typed mask array by elementwise-comparing one or more input arrays against user-supplied thresholds, combined through an arbitrarily-nested tree of AND/OR/invert `ArrayThresholdSet`s. Verification uses a **Class 1 (Analytical) oracle**: every comparison operator, invert flag, union operator, nesting depth, and both the mask-output and source-input `DataType` are exhaustively hand-derived and asserted in `MultiThresholdObjectsTest.cpp` (9 `TEST_CASE` groups, all passing). 23 of 24 algorithm/preflight code paths are exercised; the filter has not yet been diffed against legacy DREAM3D 6.5.171. + +## Algorithm Relationship + +*Classification:* **Rewrite** + +*Evidence:* `SimplnxCoreLegacyUUIDMapping.hpp` maps **two distinct legacy SIMPL UUIDs** to this single SIMPLNX filter's UUID: + +``` +014b7300-cf36-5ede-a751-5faf9b119dae → MultiThresholdObjectsFilter // MultiThresholdObjects ("Threshold Objects") +686d5393-2b02-5c86-b887-dd81a8ae80f2 → MultiThresholdObjectsFilter // MultiThresholdObjects2 ("Threshold Objects (Advanced)") +``` + +`FromSIMPLJson()` correspondingly branches on which legacy UUID (or, for 6.4 pipelines lacking a UUID, which legacy class name) produced the incoming JSON: the basic `MultiThresholdObjects` source is read through `ComparisonSelectionFilterParameterConverter` (flat, AND-only comparison list) and the advanced `MultiThresholdObjects2` source is read through `ComparisonSelectionAdvancedFilterParameterConverter` (nested AND/OR comparison sets). Both are converted into the same `ArrayThresholdSet` argument. This is **not** a line-by-line port of a single legacy algorithm — it's a consolidation of two independently-shipped legacy filters into one, which is why the classification is **Rewrite** rather than Port, per `vv_policy.md`: *"keeping [a UUID relationship] is a claim of functional equivalence... The Deviations file must defend the claim."* Here the claim is stronger than usual — that the merged filter reproduces each of the two legacy filters' behavior when configured equivalently to it. SIMPL 6.4/6.5 conversion fixtures exist at `test/simpl_conversion/6_4/MultiThresholdObjectsFilter.json` and `test/simpl_conversion/6_5/MultiThresholdObjectsFilter.json`, but these only assert the *argument conversion* round-trips correctly, not that execution output matches either legacy filter — that's the outstanding legacy A/B (see Deviations file). + +*Structural differences from each legacy source (none independently confirmed by a legacy A/B yet):* + +1. **Consolidation itself** — one `ArrayThresholdSet` tree replaces two separate legacy parameter models (flat list vs. nested set); the flat legacy model is representable as a one-level `ArrayThresholdSet`, but this equivalence has not been verified end-to-end against either legacy filter's actual output. +2. Multi-component index selection added (`#1184`, `32837a30f`) — not present in the legacy `Threshold Objects` filter (scalar-only comparison); unclear whether `Threshold Objects (Advanced)` had an equivalent. +3. Custom TRUE/FALSE mask output values added (`#669`, `b65210cf3`) — additive parameter; unconfirmed whether either legacy filter had this option or if it's SIMPLNX-only. +4. Default mask output `DataType` changed to `uint8` (`#1502`, `49919b086`) — unconfirmed against either legacy filter's default. +5. `executeImpl()` body moved into `Algorithms/MultiThresholdObjects.{hpp,cpp}` (`#1544`, `8381d1dd5`) — structural only, no behavior change (internal to SIMPLNX, not a legacy-relationship concern). + +*Material PRs since baseline (2025-10-01):* none identified beyond the deltas above and this branch's `vv/MultiThresholdObjects` restructuring + test work. + +## Oracle + +*Class:* **1 (Analytical)** + +*Applied:* For a single threshold, `expected[i] = COMPARISON(input[i], value)` (optionally inverted); for a component-indexed array, `input[i]` is replaced by `input[i][componentIndex]`. For a threshold set, `expected` is the boolean combination of each member's own `expected` value: the first member always seeds the accumulator, and the configured `UnionOperator` (AND/OR) combines each subsequent member; the whole set's `expected` is inverted again if the set itself is marked inverted. Every free variable in this formula — comparison operator, invert flag, union operator, set nesting, component index, mask output `DataType`, and source-array `DataType` — is enumerated directly against this closed-form definition in the test file's `Expected*Mask` helper functions, independent of the algorithm's own C++ control flow (`ThresholdFilterHelper`, `InsertThreshold`, `ApplyThresholdValues`). + +*Encoded:* `test/MultiThresholdObjectsTest.cpp` — + +- `Valid Single Thresholds: Int` / `: Float` / `: Int Multi-Component` — comparison operator × invert × (component index for multi-component) sweep, via `ExpectedIntSingleComponentMask` / `ExpectedFloatSingleComponentMask` / `ExpectedIntMultiComponentMask` +- `Valid Threshold Sets` — 5 hand-built AND / OR / nested-set / nested-set-with-OR / nested-set-with-OR+invert configurations (`CreateThresholdSet1`–`5`), via `ExpectedThresholdSet1Mask`–`5` +- `Valid Execution, Mask DataType` — 11 mask-output `DataType`s +- `Valid Execution, Input Array DataType` — 11 source-array `DataType`s +- `Invalid Execution`, `Invalid Execution - Out of Bounds Custom Values` (9 numeric types), `Invalid Execution - Boolean Custom Values` — negative-path fixtures + +9 `TEST_CASE` groups (17 ctest entries, counting the 9 `TEMPLATE_TEST_CASE` type instantiations separately), all pass at HEAD. + +*Second-engineer review:* Skipped — recorded reason: the oracle is elementwise comparison plus boolean set algebra (AND/OR/invert), and the test matrix enumerates it exhaustively (every operator × invert × union operator × nesting × both `DataType` axes) rather than sampling a single hand-derivation, substituting breadth for independent derivation review. **This is not a substitute for a named second-engineer pass** — it is recorded here as an outstanding gate for promotion past DRAFT, not a completed one. + +## Code path coverage + +**23 of 24 paths exercised.** The one gap is low-risk (row 13). + +Source: `src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/MultiThresholdObjects.cpp` (~255 lines), plus 7 preflight-only paths in `src/Plugins/SimplnxCore/src/SimplnxCore/Filters/MultiThresholdObjectsFilter.cpp`. + +Two logical stages: **(a) preflight** validates the threshold set / mask-type / custom-value configuration and stages the output `CreateArrayAction`; **(b) algorithm** recursively evaluates the `ArrayThresholdSet` tree (per-array comparison → per-set union/invert/replace combination) and writes the result into the mask array via a type-dispatched functor. + +| # | Stage | Path | Test case | +|----|-------------------|------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------| +| 1 | (a) Preflight | `thresholdPaths.empty()` → error `-4000` | `Invalid Execution` — "Empty ArrayThresholdSet" | +| 2 | (a) Preflight | Tuple-count mismatch across threshold arrays → `ErrorCodes::UnequalTuples` | `Invalid Execution` — "Mismatching Tuples in Threshold Arrays" | +| 3 | (a) Preflight | `componentIndex >= numComponents` (via `CheckComponentIndicesInThresholds`, recurses into nested sets) → `ErrorCodes::InvalidComponentIndex` | `Invalid Execution` — "Out of Bounds Component Index" | +| 4 | (a) Preflight | `maskArrayType == boolean && useCustomTrueValue` → `CustomTrueWithBoolean` | `Invalid Execution - Boolean Custom Values` — "Custom True Value" | +| 5 | (a) Preflight | `maskArrayType == boolean && useCustomFalseValue` → `CustomFalseWithBoolean` | `Invalid Execution - Boolean Custom Values` — "Custom False Value" | +| 6 | (a) Preflight | `useCustomTrueValue` value outside `[min,max]` of mask type → `CustomTrueOutOfBounds` | `TEMPLATE_TEST_CASE …Out of Bounds Custom Values` — "True Value < Minimum" / "> Maximum", all 9 numeric types | +| 7 | (a) Preflight | `useCustomFalseValue` value outside bounds → `CustomFalseOutOfBounds` | same — "False Value < Minimum" / "> Maximum" | +| 8 | (a) Preflight | Success → stage `CreateArrayAction` for mask output | every "Valid …" test | +| 9 | (b) Algorithm | `ComparisonType::LessThan` | "ArrayThreshold: <" (int / float / multi-component) | +| 10 | (b) Algorithm | `ComparisonType::GreaterThan` | "ArrayThreshold: >" | +| 11 | (b) Algorithm | `ComparisonType::Operator_Equal` | "ArrayThreshold: ==" | +| 12 | (b) Algorithm | `ComparisonType::Operator_NotEqual` | "ArrayThreshold: !=" | +| 13 | (b) Algorithm | `else` → `throw std::runtime_error` (unrecognized comparison type) | *Not directly tested. Unreachable via the public `ComparisonType` enum — all enumerators are exercised by rows 9–12.* | +| 14 | (b) Algorithm | `InsertThreshold` with `inverse == true` (flip before combine) | `isInverted = GENERATE(false, true)` in every single-threshold and threshold-set test | +| 15 | (b) Algorithm | `InsertThreshold` with `inverse == false` | same | +| 16 | (b) Algorithm | Combine with `UnionOperator::Or` | `CreateThresholdSet2` (threshold2 = Or), `CreateThresholdSet4`/`5` (nested-set union = Or) | +| 17 | (b) Algorithm | Combine with `UnionOperator::And` (else branch) | `CreateThresholdSet1` (threshold2/3 = And), `CreateThresholdSet3` default nested And | +| 18 | (b) Algorithm | `ApplyThresholdValues` with `replaceInput == true` (first item in a set forces Or regardless of configured operator) | implicit in every threshold set — first entry of every `CreateThresholdSet*` | +| 19 | (b) Algorithm | `ApplyThresholdValues` with `replaceInput == false` (honors configured operator for later items) | same sets, 2nd/3rd entries | +| 20 | (b) Algorithm | `ThresholdSet` recursion — item is a nested `ArrayThresholdSet` | `CreateThresholdSet3`/`4`/`5` (set-of-sets) | +| 21 | (b) Algorithm | `ThresholdSet` — item is a leaf `ArrayThreshold` | all tests | +| 22 | (b) Algorithm | `ThresholdSetFunctor` dispatch on **mask (output) DataType** | `Valid Execution, Mask DataType` — boolean + int8/16/32/64 + uint8/16/32/64 + float32/64, all 11 types | +| 23 | (b) Algorithm | `ExecuteThresholdHelper` dispatch on **source array's DataType** | `Valid Execution, Input Array DataType` — int8/16/32/64 + uint8/16/32/64 + float32/64 + boolean, all 11 types | +| 24 | (b) Algorithm | Multi-component `componentIndex != 0` selection | `Valid Single Thresholds: Int Multi-Component` (`componentIndex = GENERATE(0,1,2)`), plus `componentIndex=1` in Set1, `=0` in Set2 | + +Not counted as an algorithm/preflight path: the "Empty ArrayThreshold DataPath" section of `Invalid Execution` exercises `ArrayThresholdsParameter`'s own path-existence validation, which runs before `preflightImpl` is called — it's a parameter-layer gate, not code inside this filter or algorithm. + +Also note: `k_MismatchingComponentsArrayPath` (test file, line 31) is a leftover unused `DataPath` constant — the array it used to name was removed when `Valid Execution, Input Array DataType` was added. Not a coverage gap (the filter has no cross-array component-count check), just dead test-source code worth deleting. + +## Test inventory + +| Test case | Status | Notes | +|-----------|--------|-------| +| `Valid Single Thresholds: Int` | kept | `GENERATE` over 8 threshold values × 2 invert states, 4 `SECTION`s (`>`, `<`, `==`, `!=`) against `k_TestArrayIntPath`; every tuple checked via `ExpectedIntSingleComponentMask`. | +| `Valid Single Thresholds: Float` | kept | Same sweep against `k_TestArrayFloatPath` via `ExpectedFloatSingleComponentMask`. | +| `Valid Single Thresholds: Int Multi-Component` | kept | Adds `componentIndex = GENERATE(0,1,2)` against `k_MultiComponentArrayPath`. | +| `Valid Threshold Sets` | kept | 5 `SECTION`s (`ArraySet 1`–`5`) covering AND, OR, nested-set, nested-set-with-OR, and nested-set-with-OR+invert combinations, each × `isInverted`. | +| `Invalid Execution` | kept | 4 `SECTION`s: empty threshold set (`-4000`), empty threshold `DataPath` (parameter-layer validation), out-of-bounds component index (`InvalidComponentIndex`), mismatched tuple counts (`UnequalTuples`). | +| `Invalid Execution - Out of Bounds Custom Values` (`TEMPLATE_TEST_CASE`) | kept | 9 numeric-type instantiations × 4 `SECTION`s (true/false value below minimum / above maximum) — `CustomTrueOutOfBounds` / `CustomFalseOutOfBounds`. | +| `Invalid Execution - Boolean Custom Values` | kept | 2 `SECTION`s — custom TRUE/FALSE value rejected when mask type is `boolean`. | +| `Valid Execution, Mask DataType` | kept | 11 `SECTION`s, one per mask-output `DataType` (int8…float64; boolean covered via the default mask type used throughout the other `TEST_CASE`s). | +| `Valid Execution, Input Array DataType` | new-for-V&V (`d18b0f34d`, 2026-07-23) | 11 `SECTION`s, one per **source-array** `DataType` (int8…float64, bool) — closes the code-path gap on row 23 identified during path enumeration. | + +## Exemplar archive + +None. All fixtures for this filter are constructed in-memory in `test/MultiThresholdObjectsTest.cpp` (`CreateTestDataStructure()`, `CreateTestDataStructure2()`); there is no `.dream3d` exemplar and no `download_test_data()` entry in `test/CMakeLists.txt` for `MultiThresholdObjectsFilter`. No provenance sidecar is needed. + +## Deviations from DREAM3D 6.5.171 + +- Legacy comparison has **not been run**. 0 deviation entries exist. See `vv/deviations/MultiThresholdObjectsFilter.md` for the reason and the configurations to prioritize once a legacy build is available. diff --git a/src/Plugins/SimplnxCore/vv/deviations/MultiThresholdObjectsFilter.md b/src/Plugins/SimplnxCore/vv/deviations/MultiThresholdObjectsFilter.md new file mode 100644 index 0000000000..091f1190f8 --- /dev/null +++ b/src/Plugins/SimplnxCore/vv/deviations/MultiThresholdObjectsFilter.md @@ -0,0 +1,41 @@ +# Deviations from DREAM3D 6.5.171: MultiThresholdObjectsFilter + +This file lists every documented behavioral difference between this SIMPLNX filter and its DREAM3D 6.5.171 equivalent. + +Entries are referenced by stable ID (`MultiThresholdObjectsFilter-D`) from the V&V report and from public migration guidance. The ID is stable across renames; the Filter UUID field is the permanent cross-reference anchor. + +--- + +## Status: comparison not yet run + +No local build of legacy DREAM3D 6.5.171 is available in this environment. No runtime A/B has been performed against **either** legacy predecessor: + +- **Threshold Objects** (`MultiThresholdObjects`, SIMPL UUID `014b7300-cf36-5ede-a751-5faf9b119dae`) — flat, AND-only comparison list +- **Threshold Objects (Advanced)** (`MultiThresholdObjects2`, SIMPL UUID `686d5393-2b02-5c86-b887-dd81a8ae80f2`) — nested AND/OR comparison sets + +**0 deviation entries exist as of this DRAFT.** + +Per `vv_policy.md`'s one ordering rule ("pick the oracle before running any DREAM3D 6.5.171 comparison"), the V&V report's Class 1 (Analytical) oracle already establishes SIMPLNX correctness independently of legacy — see `vv/MultiThresholdObjectsFilter.md`. The legacy A/B remains outstanding and is required before this filter's status can move past DRAFT; it is not a precondition for the oracle work already done. + +**Because the Algorithm Relationship is classified as Rewrite** (this filter consolidates two independently-shipped legacy filters into one — see the V&V report), the burden here is higher than a straight port: the eventual comparison must independently confirm output equivalence against **both** legacy filters, run separately — + +1. Configurations expressible in the legacy flat (AND-only) model, compared against **Threshold Objects**. +2. Configurations using nested AND/OR sets or per-set invert, compared against **Threshold Objects (Advanced)**. + +A clean result on only one of the two legacy filters is not sufficient to close this Rewrite's defense — both source filters must be reconciled before the merged UUID's functional-equivalence claim can be considered verified. + +## Filter UUID + +`4246245e-1011-4add-8436-0af6bed19228` + +## Other configurations to prioritize once a legacy build is available + +Beyond running the two legacy comparisons described above, these individual parameter additions are the other likely sources of drift — not observed deviations, just a prioritized test plan for the eventual A/B: + +1. **Multi-component index selection** (`#1184` addition) — not present in legacy `Threshold Objects`; unconfirmed whether `Threshold Objects (Advanced)` had an equivalent. Comparison is only meaningful against whichever legacy filter (if either) has this feature. +2. **Custom TRUE/FALSE mask output values** (`#669` addition) — compare with it left at legacy defaults first, then with custom values set. +3. **Default mask output `DataType`** — SIMPLNX defaults to `uint8` (`#1502`); confirm what each legacy filter's default was and whether any migration guidance is needed for pipelines that relied on the default rather than explicitly setting it. + +## Entries + +No entries yet. When a comparison surfaces an actual behavioral difference, add it here following the stable-ID convention (`MultiThresholdObjectsFilter-D1`, `-D2`, …) with fields: Deviation ID, Filter UUID, Status, Symptom, Root cause (`bug` | `precision` | `order of operations` | `library` | `algorithmic choice`), Affected users, Recommendation.