diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/MultiThresholdObjectsFilter.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/MultiThresholdObjectsFilter.cpp index a42f2b066f..7086c987bb 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/MultiThresholdObjectsFilter.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/MultiThresholdObjectsFilter.cpp @@ -19,93 +19,77 @@ namespace nx::core { namespace { -template -class ThresholdFilterHelper +Result<> CheckComponentIndicesInThresholds(const ArrayThresholdSet& thresholds, const DataStructure& dataStructure) { -public: - ThresholdFilterHelper(nx::core::ArrayThreshold::ComparisonType compType, nx::core::ArrayThreshold::ComparisonValue compValue, std::vector& output) - : m_ComparisonOperator(compType) - , m_ComparisonValue(compValue) - , m_Output(output) + Result<> finalResult; + for(const auto& threshold : thresholds.getArrayThresholds()) { - } - - ~ThresholdFilterHelper() = default; - - /** - * @brief - */ - template - void filterDataLessThan(const AbstractDataStore& m_Input, T trueValue, T falseValue) - { - size_t m_NumValues = m_Input.getNumberOfTuples(); - T value = static_cast(m_ComparisonValue); - for(size_t i = 0; i < m_NumValues; ++i) + const IArrayThreshold* thresholdPtr = threshold.get(); + if(const auto* comparisonSet = dynamic_cast(thresholdPtr); comparisonSet != nullptr) { - m_Output[i] = (m_Input[i] < value) ? trueValue : falseValue; + Result<> result = CheckComponentIndicesInThresholds(*comparisonSet, dataStructure); + finalResult = MergeResults(std::move(result), std::move(finalResult)); } - } - - /** - * @brief - */ - template - void filterDataGreaterThan(const AbstractDataStore& m_Input, T trueValue, T falseValue) - { - size_t m_NumValues = m_Input.getNumberOfTuples(); - T value = static_cast(m_ComparisonValue); - for(size_t i = 0; i < m_NumValues; ++i) + else if(const auto* comparisonValue = dynamic_cast(thresholdPtr); comparisonValue != nullptr) { - m_Output[i] = (m_Input[i] > value) ? trueValue : falseValue; + DataPath dataPath = comparisonValue->getArrayPath(); + const auto& currentDataArray = dataStructure.getDataRefAs(dataPath); + usize index = comparisonValue->getComponentIndex(); + usize numComponents = currentDataArray.getNumberOfComponents(); + if(index >= currentDataArray.getNumberOfComponents()) + { + finalResult = MergeResults(MakeErrorResult(to_underlying(MultiThresholdObjectsFilter::ErrorCodes::InvalidComponentIndex), + fmt::format("Array '{}' has {} component(s) but index {} was selected", dataPath.toString(), numComponents, index)), + std::move(finalResult)); + } } } + return finalResult; +} - /** - * @brief - */ - template - void filterDataEqualTo(const AbstractDataStore& m_Input, T trueValue, T falseValue) +template +class ThresholdFilterHelper +{ +public: + ThresholdFilterHelper(ArrayThreshold::ComparisonType compType, ArrayThreshold::ComparisonValue compValue, usize componentIndex, std::vector& output) + : m_ComparisonOperator(compType) + , m_ComparisonValue(compValue) + , m_ComponentIndex(componentIndex) + , m_Output(output) { - size_t m_NumValues = m_Input.getNumberOfTuples(); - T value = static_cast(m_ComparisonValue); - for(size_t i = 0; i < m_NumValues; ++i) - { - m_Output[i] = (m_Input[i] == value) ? trueValue : falseValue; - } } - /** - * @brief - */ - template - void filterDataNotEqualTo(const AbstractDataStore& m_Input, T trueValue, T falseValue) + template + void filterDataWithComparision(const AbstractDataStore& m_Input, T trueValue, T falseValue) { - size_t m_NumValues = m_Input.getNumberOfTuples(); + size_t numTuples = m_Input.getNumberOfTuples(); T value = static_cast(m_ComparisonValue); - for(size_t i = 0; i < m_NumValues; ++i) + for(size_t tupleIndex = 0; tupleIndex < numTuples; ++tupleIndex) { - m_Output[i] = (m_Input[i] != value) ? trueValue : falseValue; + T inputValue = m_Input.getComponentValue(tupleIndex, m_ComponentIndex); + T outputValue = CompT{}(inputValue, value) ? trueValue : falseValue; + m_Output[tupleIndex] = outputValue; } } - template - void filterData(const AbstractDataStore& input, Type trueValue, Type falseValue) + template + void filterData(const AbstractDataStore& input, T trueValue, T falseValue) { if(m_ComparisonOperator == ArrayThreshold::ComparisonType::LessThan) { - filterDataLessThan(input, trueValue, falseValue); + filterDataWithComparision, T>(input, trueValue, falseValue); } else if(m_ComparisonOperator == ArrayThreshold::ComparisonType::GreaterThan) { - filterDataGreaterThan(input, trueValue, falseValue); + filterDataWithComparision, T>(input, trueValue, falseValue); } else if(m_ComparisonOperator == ArrayThreshold::ComparisonType::Operator_Equal) { - filterDataEqualTo(input, trueValue, falseValue); + filterDataWithComparision, T>(input, trueValue, falseValue); } else if(m_ComparisonOperator == ArrayThreshold::ComparisonType::Operator_NotEqual) { - filterDataNotEqualTo(input, trueValue, falseValue); + filterDataWithComparision, T>(input, trueValue, falseValue); } else { @@ -115,23 +99,18 @@ class ThresholdFilterHelper } private: - nx::core::ArrayThreshold::ComparisonType m_ComparisonOperator; - nx::core::ArrayThreshold::ComparisonValue m_ComparisonValue; + ArrayThreshold::ComparisonType m_ComparisonOperator; + ArrayThreshold::ComparisonValue m_ComparisonValue; + usize m_ComponentIndex = 0; std::vector& m_Output; - -public: - ThresholdFilterHelper(const ThresholdFilterHelper&) = delete; // Copy Constructor Not Implemented - ThresholdFilterHelper(ThresholdFilterHelper&&) = delete; // Move Constructor Not Implemented - ThresholdFilterHelper& operator=(const ThresholdFilterHelper&) = delete; // Copy Assignment Not Implemented - ThresholdFilterHelper& operator=(ThresholdFilterHelper&&) = delete; // Move Assignment Not Implemented }; struct ExecuteThresholdHelper { template - void operator()(ThresholdFilterHelper& helper, const IDataArray* iDataArray, Type trueValue, Type falseValue) + void operator()(ThresholdFilterHelper& helper, const IDataArray& iDataArray, Type trueValue, Type falseValue) { - const auto& dataStore = iDataArray->template getIDataStoreRefAs>(); + const auto& dataStore = iDataArray.template getIDataStoreRefAs>(); helper.template filterData(dataStore, trueValue, falseValue); } }; @@ -167,30 +146,26 @@ void InsertThreshold(usize numItems, AbstractDataStore& currentStore, nx::cor } template -void ThresholdValue(std::shared_ptr& comparisonValue, const DataStructure& dataStructure, AbstractDataStore& outputResultStore, int32_t& err, bool replaceInput, bool inverse, - T trueValue, T falseValue) +void ThresholdValue(const ArrayThreshold& comparisonValue, const DataStructure& dataStructure, AbstractDataStore& outputResultStore, int32_t& err, bool replaceInput, bool inverse, T trueValue, + T falseValue) { - if(nullptr == comparisonValue) - { - err = -1; - return; - } - // Get the total number of tuples, create and initialize an array with FALSE to use for these results size_t totalTuples = outputResultStore.getNumberOfTuples(); std::vector tempResultVector(totalTuples, falseValue); - nx::core::ArrayThreshold::ComparisonType compOperator = comparisonValue->getComparisonType(); - nx::core::ArrayThreshold::ComparisonValue compValue = comparisonValue->getComparisonValue(); - nx::core::IArrayThreshold::UnionOperator unionOperator = comparisonValue->getUnionOperator(); + nx::core::ArrayThreshold::ComparisonType compOperator = comparisonValue.getComparisonType(); + nx::core::ArrayThreshold::ComparisonValue compValue = comparisonValue.getComparisonValue(); + nx::core::IArrayThreshold::UnionOperator unionOperator = comparisonValue.getUnionOperator(); + + DataPath inputDataArrayPath = comparisonValue.getArrayPath(); - DataPath inputDataArrayPath = comparisonValue->getArrayPath(); + usize componentIndex = comparisonValue.getComponentIndex(); - ThresholdFilterHelper helper(compOperator, compValue, tempResultVector); + ThresholdFilterHelper helper(compOperator, compValue, componentIndex, tempResultVector); - const auto* iDataArray = dataStructure.getDataAs(inputDataArrayPath); + const auto& iDataArray = dataStructure.getDataRefAs(inputDataArrayPath); - ExecuteDataFunction(ExecuteThresholdHelper{}, iDataArray->getDataType(), helper, iDataArray, trueValue, falseValue); + ExecuteDataFunction(ExecuteThresholdHelper{}, iDataArray.getDataType(), helper, iDataArray, trueValue, falseValue); if(replaceInput) { @@ -214,44 +189,36 @@ void ThresholdValue(std::shared_ptr& comparisonValue, const Data struct ThresholdValueFunctor { template - void operator()(std::shared_ptr& comparisonValue, const DataStructure& dataStructure, IDataArray* outputResultArray, int32_t& err, bool replaceInput, bool inverse, T trueValue, - T falseValue) + void operator()(const ArrayThreshold& comparisonValue, const DataStructure& dataStructure, IDataArray& outputResultArray, int32_t& err, bool replaceInput, bool inverse, T trueValue, T falseValue) { // Traditionally we would do a check to ensure we get a valid pointer, I'm forgoing that check because it // was essentially done in the preflight part. - ThresholdValue(comparisonValue, dataStructure, outputResultArray->template getIDataStoreRefAs>(), err, replaceInput, inverse, trueValue, falseValue); + ThresholdValue(comparisonValue, dataStructure, outputResultArray.template getIDataStoreRefAs>(), err, replaceInput, inverse, trueValue, falseValue); } }; template -void ThresholdSet(std::shared_ptr& inputComparisonSet, const DataStructure& dataStructure, AbstractDataStore& outputResultStore, int32_t& err, bool replaceInput, bool inverse, - T trueValue, T falseValue) +void ThresholdSet(const ArrayThresholdSet& inputComparisonSet, const DataStructure& dataStructure, AbstractDataStore& outputResultStore, int32_t& err, bool replaceInput, bool inverse, T trueValue, + T falseValue) { - if(nullptr == inputComparisonSet) - { - return; - } - // Get the total number of tuples, create and initialize an array with FALSE to use for these results size_t totalTuples = outputResultStore.getNumberOfTuples(); std::vector tempResultVector(totalTuples, falseValue); - T firstValueFound = 0; + bool firstValueFound = false; - ArrayThresholdSet::CollectionType thresholds = inputComparisonSet->getArrayThresholds(); + ArrayThresholdSet::CollectionType thresholds = inputComparisonSet.getArrayThresholds(); for(const std::shared_ptr& threshold : thresholds) { - if(std::dynamic_pointer_cast(threshold)) + const IArrayThreshold* thresholdPtr = threshold.get(); + if(const auto* comparisonSet = dynamic_cast(thresholdPtr); comparisonSet != nullptr) { - std::shared_ptr comparisonSet = std::dynamic_pointer_cast(threshold); - ThresholdSet(comparisonSet, dataStructure, outputResultStore, err, !firstValueFound, false, trueValue, falseValue); + ThresholdSet(*comparisonSet, dataStructure, outputResultStore, err, !firstValueFound, false, trueValue, falseValue); firstValueFound = true; } - else if(std::dynamic_pointer_cast(threshold)) + else if(const auto* comparisonValue = dynamic_cast(thresholdPtr); comparisonValue != nullptr) { - std::shared_ptr comparisonValue = std::dynamic_pointer_cast(threshold); - - ThresholdValue(comparisonValue, dataStructure, outputResultStore, err, !firstValueFound, false, trueValue, falseValue); + ThresholdValue(*comparisonValue, dataStructure, outputResultStore, err, !firstValueFound, false, trueValue, falseValue); firstValueFound = true; } } @@ -271,24 +238,19 @@ void ThresholdSet(std::shared_ptr& inputComparisonSet, const else { // insert into current threshold - InsertThreshold(totalTuples, outputResultStore, inputComparisonSet->getUnionOperator(), tempResultVector, inverse, trueValue, falseValue); + InsertThreshold(totalTuples, outputResultStore, inputComparisonSet.getUnionOperator(), tempResultVector, inverse, trueValue, falseValue); } } struct ThresholdSetFunctor { template - void operator()(std::shared_ptr& inputComparisonSet, const DataStructure& dataStructure, IDataArray* outputResultArray, int32_t& err, bool replaceInput, bool inverse, T trueValue, + void operator()(const ArrayThresholdSet& inputComparisonSet, const DataStructure& dataStructure, IDataArray& outputResultArray, int32_t& err, bool replaceInput, bool inverse, T trueValue, T falseValue) { - if(outputResultArray == nullptr) - { - return; - } - // 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); + ThresholdSet(inputComparisonSet, dataStructure, outputResultArray.template getIDataStoreRefAs>(), err, replaceInput, inverse, trueValue, falseValue); } }; @@ -359,7 +321,7 @@ Parameters MultiThresholdObjectsFilter::parameters() const params.insertSeparator(Parameters::Separator{"Input Parameter(s)"}); params.insert(std::make_unique(k_ArrayThresholdsObject_Key, "Data Thresholds", "DataArray thresholds to mask", ArrayThresholdSet{}, - ArrayThresholdsParameter::AllowedComponentShapes{{1}})); + ArrayThresholdsParameter::AllowedComponentShapes{})); params.insert(std::make_unique(k_CreatedMaskType_Key, "Mask Type", "DataType used for the created Mask Array", DataType::boolean)); params.insertLinkableParameter(std::make_unique(k_UseCustomTrueValue, "Use Custom TRUE Value", "Specifies whether to output a custom TRUE value (the default value is 1)", false)); params.insert(std::make_unique>(k_CustomTrueValue, "Custom TRUE Value", "This is the custom TRUE value that will be output to the mask array", 1.0)); @@ -404,52 +366,46 @@ IFilter::PreflightResult MultiThresholdObjectsFilter::preflightImpl(const DataSt return MakePreflightErrorResult(-4000, "No data arrays were found for calculating threshold"); } - for(const auto& path : thresholdPaths) - { - if(dataStructure.getData(path) == nullptr) - { - auto errorMessage = fmt::format("Could not find DataArray at path {}.", path.toString()); - return {nonstd::make_unexpected(std::vector{Error{to_underlying(ErrorCodes::PathNotFoundError), errorMessage}})}; - } - } + DataPath firstDataPath = *(thresholdPaths.begin()); + const auto& dataArray = dataStructure.getDataRefAs(firstDataPath); - // Check for Scalar arrays + // 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.getDataAs(dataPath); - if(currentDataArray != nullptr && currentDataArray->getNumberOfComponents() != 1) + const auto& currentDataArray = dataStructure.getDataRefAs(dataPath); + usize currentNumTuples = currentDataArray.getNumberOfTuples(); + if(currentNumTuples != numTuples) { - auto errorMessage = fmt::format("Data Array is not a Scalar Data Array. Data Arrays must only have a single component. '{}:{}'", dataPath.toString(), currentDataArray->getNumberOfComponents()); - return {nonstd::make_unexpected(std::vector{Error{to_underlying(ErrorCodes::NonScalarArrayFound), errorMessage}})}; + 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); } - } - - // Check that all arrays the number of tuples match - DataPath firstDataPath = *(thresholdPaths.begin()); - const auto* dataArray = dataStructure.getDataAs(firstDataPath); - size_t numTuples = dataArray->getNumberOfTuples(); - - for(const auto& dataPath : thresholdPaths) - { - const auto* currentDataArray = dataStructure.getDataAs(dataPath); - if(numTuples != currentDataArray->getNumberOfTuples()) + usize currentNumComponents = currentDataArray.getNumberOfComponents(); + if(currentNumComponents != numComponents) { auto errorMessage = - fmt::format("Data Arrays do not have same equal number of tuples. '{}:{}' and '{}'", firstDataPath.toString(), numTuples, dataPath.toString(), currentDataArray->getNumberOfTuples()); - return {nonstd::make_unexpected(std::vector{Error{to_underlying(ErrorCodes::UnequalTuples), 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); + if(componentIndicesResult.invalid()) + { + return {ConvertInvalidResult(std::move(componentIndicesResult))}; + } + if(maskArrayType == DataType::boolean) { if(useCustomTrueValue) { - return {nonstd::make_unexpected(std::vector{Error{to_underlying(ErrorCodes::CustomTrueWithBoolean), "Cannot use custom TRUE value with a boolean Mask Type."}})}; + return MakePreflightErrorResult(to_underlying(ErrorCodes::CustomTrueWithBoolean), "Cannot use custom TRUE value with a boolean Mask Type."); } if(useCustomFalseValue) { - return {nonstd::make_unexpected(std::vector{Error{to_underlying(ErrorCodes::CustomFalseWithBoolean), "Cannot use custom FALSE value with a boolean Mask Type."}})}; + return MakePreflightErrorResult(to_underlying(ErrorCodes::CustomFalseWithBoolean), "Cannot use custom FALSE value with a boolean Mask Type."); } } @@ -459,7 +415,7 @@ IFilter::PreflightResult MultiThresholdObjectsFilter::preflightImpl(const DataSt if(result.invalid()) { auto errorMessage = fmt::format("Custom TRUE value ({}) is outside the bounds of the chosen Mask Type ({}).", customTrueValue, DataTypeToString(maskArrayType)); - return {nonstd::make_unexpected(std::vector{Error{to_underlying(ErrorCodes::CustomTrueOutOfBounds), errorMessage}})}; + return MakePreflightErrorResult(to_underlying(ErrorCodes::CustomTrueOutOfBounds), errorMessage); } } @@ -469,13 +425,13 @@ IFilter::PreflightResult MultiThresholdObjectsFilter::preflightImpl(const DataSt if(result.invalid()) { auto errorMessage = fmt::format("Custom FALSE value ({}) is outside the bounds of the chosen Mask Type ({}).", customFalseValue, DataTypeToString(maskArrayType)); - return {nonstd::make_unexpected(std::vector{Error{to_underlying(ErrorCodes::CustomFalseOutOfBounds), errorMessage}})}; + return MakePreflightErrorResult(to_underlying(ErrorCodes::CustomFalseOutOfBounds), errorMessage); } } // Create the output boolean array auto action = - std::make_unique(maskArrayType, dataArray->getIDataStore()->getTupleShape(), std::vector{1}, firstDataPath.replaceName(maskArrayName), dataArray->getDataFormat()); + std::make_unique(maskArrayType, dataArray.getIDataStoreRef().getTupleShape(), std::vector{1}, firstDataPath.replaceName(maskArrayName), dataArray.getDataFormat()); OutputActions actions; actions.appendAction(std::move(action)); @@ -504,17 +460,16 @@ Result<> MultiThresholdObjectsFilter::executeImpl(DataStructure& dataStructure, ArrayThresholdSet::CollectionType thresholdSet = thresholdsObject.getArrayThresholds(); for(const std::shared_ptr& threshold : thresholdSet) { - if(std::dynamic_pointer_cast(threshold)) + const IArrayThreshold* thresholdPtr = threshold.get(); + if(const auto* comparisonSet = dynamic_cast(thresholdPtr); comparisonSet != nullptr) { - std::shared_ptr comparisonSet = std::dynamic_pointer_cast(threshold); - ExecuteDataFunction(ThresholdSetFunctor{}, maskArrayType, comparisonSet, dataStructure, dataStructure.getDataAs(maskArrayPath), err, !firstValueFound, thresholdsObject.isInverted(), - trueValue, falseValue); + ExecuteDataFunction(ThresholdSetFunctor{}, maskArrayType, *comparisonSet, dataStructure, dataStructure.getDataRefAs(maskArrayPath), err, !firstValueFound, + thresholdsObject.isInverted(), trueValue, falseValue); firstValueFound = true; } - else if(std::dynamic_pointer_cast(threshold)) + else if(const auto* comparisonValue = dynamic_cast(thresholdPtr); comparisonValue != nullptr) { - std::shared_ptr comparisonValue = std::dynamic_pointer_cast(threshold); - ExecuteDataFunction(ThresholdValueFunctor{}, maskArrayType, comparisonValue, dataStructure, dataStructure.getDataAs(maskArrayPath), err, !firstValueFound, + ExecuteDataFunction(ThresholdValueFunctor{}, maskArrayType, *comparisonValue, dataStructure, dataStructure.getDataRefAs(maskArrayPath), err, !firstValueFound, thresholdsObject.isInverted(), trueValue, falseValue); firstValueFound = true; } diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/MultiThresholdObjectsFilter.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/MultiThresholdObjectsFilter.hpp index d07a49390a..244f0521b6 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/MultiThresholdObjectsFilter.hpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/MultiThresholdObjectsFilter.hpp @@ -37,13 +37,13 @@ class SIMPLNXCORE_EXPORT MultiThresholdObjectsFilter : public IFilter enum ErrorCodes : int64 { - PathNotFoundError = -178, - NonScalarArrayFound = -4001, + UnequalComponents = -4001, UnequalTuples = -4002, CustomTrueWithBoolean = -4003, CustomFalseWithBoolean = -4004, CustomTrueOutOfBounds = -4005, - CustomFalseOutOfBounds = -4006 + CustomFalseOutOfBounds = -4006, + InvalidComponentIndex = -4007 }; /** diff --git a/src/Plugins/SimplnxCore/test/MultiThresholdObjectsTest.cpp b/src/Plugins/SimplnxCore/test/MultiThresholdObjectsTest.cpp index 7b0f6722dc..ffb74d886f 100644 --- a/src/Plugins/SimplnxCore/test/MultiThresholdObjectsTest.cpp +++ b/src/Plugins/SimplnxCore/test/MultiThresholdObjectsTest.cpp @@ -14,13 +14,15 @@ namespace const std::string k_TestArrayFloatName = "TestArrayFloat"; const std::string k_TestArrayIntName = "TestArrayInt"; const std::string k_ThresholdArrayName = "ThresholdArray"; +const std::string k_MultiComponentArrayName = "MultiComponent"; const DataPath k_ImageCellDataName({k_ImageGeometry, k_CellData}); const DataPath k_TestArrayFloatPath = k_ImageCellDataName.createChildPath(k_TestArrayFloatName); const DataPath k_TestArrayIntPath = k_ImageCellDataName.createChildPath(k_TestArrayIntName); +const DataPath k_MultiComponentArrayPath = k_ImageCellDataName.createChildPath(k_MultiComponentArrayName); const DataPath k_ThresholdArrayPath = k_ImageCellDataName.createChildPath(k_ThresholdArrayName); -const DataPath k_MultiComponentArrayPath = k_ImageCellDataName.createChildPath("MultiComponentArray"); +const DataPath k_MismatchingComponentsArrayPath = k_ImageCellDataName.createChildPath("MismatchingComponentsArray"); const DataPath k_MismatchingTuplesArrayPath({"MismatchingTuplesArray"}); DataStructure CreateTestDataStructure() @@ -34,24 +36,34 @@ DataStructure CreateTestDataStructure() std::vector tDims = {20}; std::vector cDims = {1}; + std::vector cDimsMulti = {3}; float fnum = 0.0f; int inum = 0; 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()); + Int32Array* multiComponentData = Int32Array::CreateWithStore(dataStructure, k_MultiComponentArrayName, tDims, cDimsMulti, am->getId()); - Float32Array* invalid1 = Float32Array::CreateWithStore(dataStructure, k_MultiComponentArrayPath.getTargetName(), tDims, std::vector{3}, 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); + usize numComponents = multiComponentData->getNumberOfComponents(); + int32 sign = 1; + // Fill the float array with {.01,.02,.03,.04,.05,.06,.07,.08,.09,.10,.11,.12,.13,.14,.15.,16,.17,.18,.19,.20} // Fill the int array with { 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19 } + // Fill multi-component array with {{0, 0, 0}, {1, -1, 1}, {-2, 2, -2}, ..., {17, -17, 17}, {-18, 18, -18}, {19, -19, 19}} for(usize i = 0; i < 20; i++) { fnum += 0.01f; (*data)[i] = fnum; // float array (*data1)[i] = inum; // int array + multiComponentData->setComponent(i, 0, i * -sign); + multiComponentData->setComponent(i, 1, i * sign); + multiComponentData->setComponent(i, 2, i * -sign); + sign *= -1; ++inum; } return dataStructure; @@ -221,13 +233,13 @@ TEST_CASE("SimplnxCore::MultiThresholdObjects: Invalid Execution", "[SimplnxCore 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)); - args.insertOrAssign(MultiThresholdObjectsFilter::k_CreatedDataName_Key, std::make_any(k_ThresholdArrayName)); } SECTION("Empty ArrayThreshold DataPath") { @@ -238,19 +250,33 @@ TEST_CASE("SimplnxCore::MultiThresholdObjects: Invalid Execution", "[SimplnxCore thresholdSet.setArrayThresholds({threshold}); args.insertOrAssign(MultiThresholdObjectsFilter::k_ArrayThresholdsObject_Key, std::make_any(thresholdSet)); - args.insertOrAssign(MultiThresholdObjectsFilter::k_CreatedDataName_Key, std::make_any(k_ThresholdArrayName)); } - SECTION("MultiComponents in Threshold Array") + 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_MultiComponentArrayPath); + 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)); - args.insertOrAssign(MultiThresholdObjectsFilter::k_CreatedDataName_Key, std::make_any(k_ThresholdArrayName)); } SECTION("Mismatching Tuples in Threshold Arrays") { @@ -266,7 +292,6 @@ TEST_CASE("SimplnxCore::MultiThresholdObjects: Invalid Execution", "[SimplnxCore thresholdSet.setArrayThresholds({threshold1, threshold2}); args.insertOrAssign(MultiThresholdObjectsFilter::k_ArrayThresholdsObject_Key, std::make_any(thresholdSet)); - args.insertOrAssign(MultiThresholdObjectsFilter::k_CreatedDataName_Key, std::make_any(k_ThresholdArrayName)); } // Preflight the filter and check result @@ -676,3 +701,53 @@ TEST_CASE("SimplnxCore::MultiThresholdObjects: Valid Execution, DataType", "[Sim checkMaskValues(dataStructure, k_ThresholdArrayPath); } } + +TEST_CASE("SimplnxCore::MultiThresholdObjects: Valid Execution - Multicomponent", "[SimplnxCore][MultiThresholdObjects]") +{ + 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); + } + } +} diff --git a/src/Plugins/SimplnxCore/wrapping/python/simplnxpy.cpp b/src/Plugins/SimplnxCore/wrapping/python/simplnxpy.cpp index 98e6455afc..3d8606e1a8 100644 --- a/src/Plugins/SimplnxCore/wrapping/python/simplnxpy.cpp +++ b/src/Plugins/SimplnxCore/wrapping/python/simplnxpy.cpp @@ -670,6 +670,7 @@ PYBIND11_MODULE(simplnx, mod) arrayThreshold.def_property("array_path", &ArrayThreshold::getArrayPath, &ArrayThreshold::setArrayPath); arrayThreshold.def_property("value", &ArrayThreshold::getComparisonValue, &ArrayThreshold::setComparisonValue); arrayThreshold.def_property("comparison", &ArrayThreshold::getComparisonType, &ArrayThreshold::setComparisonType); + arrayThreshold.def_property("component_index", &ArrayThreshold::getComponentIndex, &ArrayThreshold::setComponentIndex); py::class_> arrayThresholdSet(mod, "ArrayThresholdSet"); arrayThresholdSet.def(py::init<>()); diff --git a/src/simplnx/Parameters/ArrayThresholdsParameter.cpp b/src/simplnx/Parameters/ArrayThresholdsParameter.cpp index b6e78d33ec..37282d7fac 100644 --- a/src/simplnx/Parameters/ArrayThresholdsParameter.cpp +++ b/src/simplnx/Parameters/ArrayThresholdsParameter.cpp @@ -30,7 +30,7 @@ IParameter::AcceptedTypes ArrayThresholdsParameter::acceptedTypes() const //------------------------------------------------------------------------------ IParameter::VersionType ArrayThresholdsParameter::getVersion() const { - return 1; + return 2; } nlohmann::json ArrayThresholdsParameter::toJsonImpl(const std::any& value) const diff --git a/src/simplnx/Utilities/ArrayThreshold.cpp b/src/simplnx/Utilities/ArrayThreshold.cpp index d6329be91d..1d3b8de465 100644 --- a/src/simplnx/Utilities/ArrayThreshold.cpp +++ b/src/simplnx/Utilities/ArrayThreshold.cpp @@ -11,6 +11,7 @@ constexpr StringLiteral k_Inverted_Tag = "inverted"; constexpr StringLiteral k_Union_Tag = "union"; constexpr StringLiteral k_Value_Tag = "value"; constexpr StringLiteral k_ArrayPath_Tag = "array_path"; +constexpr StringLiteral k_ComponentIndex_Tag = "component_index"; constexpr StringLiteral k_Comparison_Tag = "comparison"; constexpr StringLiteral k_Thresholds_Tag = "thresholds"; @@ -114,6 +115,16 @@ void ArrayThreshold::setComparisonValue(ComparisonValue value) m_Value = value; } +usize ArrayThreshold::getComponentIndex() const +{ + return m_ComponentIndex; +} + +void ArrayThreshold::setComponentIndex(usize index) +{ + m_ComponentIndex = index; +} + ArrayThreshold::ComparisonType ArrayThreshold::getComparisonType() const { return m_Comparison; @@ -133,6 +144,7 @@ nlohmann::json ArrayThreshold::toJson() const auto json = IArrayThreshold::toJson(); json[k_Type_Tag] = k_ArrayType; json[k_ArrayPath_Tag] = getArrayPath().toString(); + json[k_ComponentIndex_Tag] = getComponentIndex(); json[k_Value_Tag] = getComparisonValue(); json[k_Comparison_Tag] = getComparisonType(); @@ -157,6 +169,8 @@ std::shared_ptr ArrayThreshold::FromJson(const nlohmann::json& j return nullptr; } threshold->setArrayPath(arrayPath.value()); + usize componentIndex = json.contains(k_ComponentIndex_Tag) ? json[k_ComponentIndex_Tag].get() : 0; + threshold->setComponentIndex(componentIndex); threshold->setComparisonType(static_cast(json[k_Comparison_Tag].get())); threshold->setComparisonValue(json[k_Value_Tag].get()); diff --git a/src/simplnx/Utilities/ArrayThreshold.hpp b/src/simplnx/Utilities/ArrayThreshold.hpp index adcc7b3886..a2e7aec8e1 100644 --- a/src/simplnx/Utilities/ArrayThreshold.hpp +++ b/src/simplnx/Utilities/ArrayThreshold.hpp @@ -75,6 +75,9 @@ class SIMPLNX_EXPORT ArrayThreshold : public IArrayThreshold [[nodiscard]] ComparisonValue getComparisonValue() const; void setComparisonValue(ComparisonValue value); + [[nodiscard]] usize getComponentIndex() const; + void setComponentIndex(usize index); + [[nodiscard]] ComparisonType getComparisonType() const; void setComparisonType(ComparisonType comparison); @@ -86,6 +89,7 @@ class SIMPLNX_EXPORT ArrayThreshold : public IArrayThreshold private: DataPath m_ArrayPath; ComparisonValue m_Value{0.0}; + usize m_ComponentIndex = 0; ComparisonType m_Comparison{ComparisonType::GreaterThan}; };