diff --git a/src/Plugins/ComplexCore/docs/MultiThresholdObjects.md b/src/Plugins/ComplexCore/docs/MultiThresholdObjects.md index eb5175ce5c..ba498c976d 100644 --- a/src/Plugins/ComplexCore/docs/MultiThresholdObjects.md +++ b/src/Plugins/ComplexCore/docs/MultiThresholdObjects.md @@ -13,11 +13,19 @@ An example of this **Filter's** use would be after EBSD data is read into DREAM. For example, an integer array contains the values 1, 2, 3, 4, 5. For a comparison value of 3 and the comparison operator greater than, the boolean threshold array produced will contain *false*, *false*, *false*, *true*, *true*. For the comparison set { *Greater Than* 2 AND *Less Than* 5} OR *Equals* 1, the boolean threshold array produced will contain *true*, *false*, *true*, *true*, *false*. +It is possible to set custom values for both the TRUE and FALSE values that will be output to the threshold array. For example, if the user selects an output threshold array type of uint32, then they could set a custom FALSE value of 5 and a custom TRUE value of 20. So then instead of outputting 0's and 1's to the threshold array, the filter would output 5's and 20's. + +**NOTE**: If custom TRUE/FALSE values are chosen, then using the resulting mask array in any other filters that require a mask array will break those other filters. This is because most other filters that require a mask array make the assumption that the true/false values are 1/0. + ## Parameters ## -| Name | Type | Description | -|------|------|-------------| +| Name | Type | Description | +|--------------------------|-----------------|----------------------------------------------------------------------------------------------------------------| | Data Arrays to Threshold | Comparison List | This is the set of criteria applied to the objects the selected arrays correspond to when doing the thresholding | +| Use Custom TRUE Value | bool | Specifies whether to output a custom TRUE value | +| Custom TRUE Value | float64 | The custom TRUE value (the default value is 1) | +| Use Custom FALSE Value | bool | Specifies whether to output a custom FALSE value | +| Custom FALSE Value | float64 | The custom FALSE value (the default value is 0) | ## Required Geometry ## diff --git a/src/Plugins/ComplexCore/src/ComplexCore/Filters/MultiThresholdObjects.cpp b/src/Plugins/ComplexCore/src/ComplexCore/Filters/MultiThresholdObjects.cpp index 8be5cdf943..46f3faf03c 100644 --- a/src/Plugins/ComplexCore/src/ComplexCore/Filters/MultiThresholdObjects.cpp +++ b/src/Plugins/ComplexCore/src/ComplexCore/Filters/MultiThresholdObjects.cpp @@ -1,10 +1,13 @@ #include "MultiThresholdObjects.hpp" +#include "complex/Common/TypeTraits.hpp" #include "complex/DataStructure/DataArray.hpp" #include "complex/Filter/Actions/CreateArrayAction.hpp" #include "complex/Parameters/ArrayThresholdsParameter.hpp" +#include "complex/Parameters/BoolParameter.hpp" #include "complex/Parameters/DataObjectNameParameter.hpp" #include "complex/Parameters/DataTypeParameter.hpp" +#include "complex/Parameters/NumberParameter.hpp" #include "complex/Utilities/ArrayThreshold.hpp" #include "complex/Utilities/FilterUtilities.hpp" @@ -14,8 +17,6 @@ namespace complex { namespace { -constexpr int64 k_PathNotFoundError = -178; - template class ThresholdFilterHelper { @@ -33,13 +34,13 @@ class ThresholdFilterHelper * @brief */ template - void filterDataLessThan(const DataArray& m_Input) + void filterDataLessThan(const DataArray& 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) { - m_Output[i] = (m_Input[i] < value); + m_Output[i] = (m_Input[i] < value) ? trueValue : falseValue; } } @@ -47,13 +48,13 @@ class ThresholdFilterHelper * @brief */ template - void filterDataGreaterThan(const DataArray& m_Input) + void filterDataGreaterThan(const DataArray& 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) { - m_Output[i] = (m_Input[i] > value); + m_Output[i] = (m_Input[i] > value) ? trueValue : falseValue; } } @@ -61,13 +62,13 @@ class ThresholdFilterHelper * @brief */ template - void filterDataEqualTo(const DataArray& m_Input) + void filterDataEqualTo(const DataArray& 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) { - m_Output[i] = (m_Input[i] == value); + m_Output[i] = (m_Input[i] == value) ? trueValue : falseValue; } } @@ -75,34 +76,34 @@ class ThresholdFilterHelper * @brief */ template - void filterDataNotEqualTo(const DataArray& m_Input) + void filterDataNotEqualTo(const DataArray& 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) { - m_Output[i] = (m_Input[i] != value); + m_Output[i] = (m_Input[i] != value) ? trueValue : falseValue; } } template - void filterData(const DataArray& input) + void filterData(const DataArray& input, Type trueValue, Type falseValue) { if(m_ComparisonOperator == ArrayThreshold::ComparisonType::LessThan) { - filterDataLessThan(input); + filterDataLessThan(input, trueValue, falseValue); } else if(m_ComparisonOperator == ArrayThreshold::ComparisonType::GreaterThan) { - filterDataGreaterThan(input); + filterDataGreaterThan(input, trueValue, falseValue); } else if(m_ComparisonOperator == ArrayThreshold::ComparisonType::Operator_Equal) { - filterDataEqualTo(input); + filterDataEqualTo(input, trueValue, falseValue); } else if(m_ComparisonOperator == ArrayThreshold::ComparisonType::Operator_NotEqual) { - filterDataNotEqualTo(input); + filterDataNotEqualTo(input, trueValue, falseValue); } else { @@ -126,10 +127,10 @@ class ThresholdFilterHelper struct ExecuteThresholdHelper { template - void operator()(ThresholdFilterHelper& helper, const IDataArray& iDataArray) + void operator()(ThresholdFilterHelper& helper, const IDataArray& iDataArray, Type trueValue, Type falseValue) { const auto& dataArray = dynamic_cast&>(iDataArray); - helper.template filterData(dataArray); + helper.template filterData(dataArray, trueValue, falseValue); } }; @@ -142,29 +143,30 @@ struct ExecuteThresholdHelper * @param inverse */ template -void InsertThreshold(usize numItems, DataArray& currentArray, complex::IArrayThreshold::UnionOperator unionOperator, std::vector& newArrayPtr, bool inverse) +void InsertThreshold(usize numItems, DataArray& currentArray, complex::IArrayThreshold::UnionOperator unionOperator, std::vector& newArrayPtr, bool inverse, T trueValue, T falseValue) { for(usize i = 0; i < numItems; i++) { // invert the current comparison if necessary if(inverse) { - newArrayPtr[i] = !newArrayPtr[i]; + newArrayPtr[i] = (newArrayPtr[i] == trueValue) ? falseValue : trueValue; } if(complex::IArrayThreshold::UnionOperator::Or == unionOperator) { - currentArray[i] = currentArray[i] || newArrayPtr[i]; + currentArray[i] = (currentArray[i] == trueValue || newArrayPtr[i] == trueValue) ? trueValue : falseValue; } - else if(!currentArray[i] || !newArrayPtr[i]) + else if(currentArray[i] == falseValue || newArrayPtr[i] == falseValue) { - currentArray[i] = false; + currentArray[i] = falseValue; } } } template -void ThresholdValue(std::shared_ptr& comparisonValue, DataStructure& dataStructure, DataPath& outputResultArrayPath, int32_t& err, bool replaceInput, bool inverse) +void ThresholdValue(std::shared_ptr& comparisonValue, DataStructure& dataStructure, DataPath& outputResultArrayPath, int32_t& err, bool replaceInput, bool inverse, T trueValue, + T falseValue) { if(nullptr == comparisonValue) { @@ -177,7 +179,7 @@ void ThresholdValue(std::shared_ptr& comparisonValue, DataStruct // Get the total number of tuples, create and initialize an array with FALSE to use for these results size_t totalTuples = outputResultArray.getNumberOfTuples(); - std::vector tempResultVector(totalTuples, false); + std::vector tempResultVector(totalTuples, falseValue); complex::ArrayThreshold::ComparisonType compOperator = comparisonValue->getComparisonType(); complex::ArrayThreshold::ComparisonValue compValue = comparisonValue->getComparisonValue(); @@ -189,7 +191,7 @@ void ThresholdValue(std::shared_ptr& comparisonValue, DataStruct const auto& iDataArray = dataStructure.getDataRefAs(inputDataArrayPath); - ExecuteDataFunction(ExecuteThresholdHelper{}, iDataArray.getDataType(), helper, iDataArray); + ExecuteDataFunction(ExecuteThresholdHelper{}, iDataArray.getDataType(), helper, iDataArray, trueValue, falseValue); if(replaceInput) { @@ -206,7 +208,7 @@ void ThresholdValue(std::shared_ptr& comparisonValue, DataStruct else { // insert into current threshold - InsertThreshold(totalTuples, outputResultArray, unionOperator, tempResultVector, inverse); + InsertThreshold(totalTuples, outputResultArray, unionOperator, tempResultVector, inverse, trueValue, falseValue); } } @@ -220,39 +222,40 @@ void ThresholdValue(std::shared_ptr& comparisonValue, DataStruct * @param inverse */ void ThresholdValue(std::shared_ptr& comparisonValue, DataStructure& dataStructure, DataPath& outputResultArrayPath, DataType maskArrayType, int32_t& err, bool replaceInput, - bool inverse) + bool inverse, float64 trueValue, float64 falseValue) { switch(maskArrayType) { case DataType::int8: - return ThresholdValue(comparisonValue, dataStructure, outputResultArrayPath, err, replaceInput, inverse); + return ThresholdValue(comparisonValue, dataStructure, outputResultArrayPath, err, replaceInput, inverse, trueValue, falseValue); case DataType::int16: - return ThresholdValue(comparisonValue, dataStructure, outputResultArrayPath, err, replaceInput, inverse); + return ThresholdValue(comparisonValue, dataStructure, outputResultArrayPath, err, replaceInput, inverse, trueValue, falseValue); case DataType::int32: - return ThresholdValue(comparisonValue, dataStructure, outputResultArrayPath, err, replaceInput, inverse); + return ThresholdValue(comparisonValue, dataStructure, outputResultArrayPath, err, replaceInput, inverse, trueValue, falseValue); case DataType::int64: - return ThresholdValue(comparisonValue, dataStructure, outputResultArrayPath, err, replaceInput, inverse); + return ThresholdValue(comparisonValue, dataStructure, outputResultArrayPath, err, replaceInput, inverse, trueValue, falseValue); case DataType::uint8: - return ThresholdValue(comparisonValue, dataStructure, outputResultArrayPath, err, replaceInput, inverse); + return ThresholdValue(comparisonValue, dataStructure, outputResultArrayPath, err, replaceInput, inverse, trueValue, falseValue); case DataType::uint16: - return ThresholdValue(comparisonValue, dataStructure, outputResultArrayPath, err, replaceInput, inverse); + return ThresholdValue(comparisonValue, dataStructure, outputResultArrayPath, err, replaceInput, inverse, trueValue, falseValue); case DataType::uint32: - return ThresholdValue(comparisonValue, dataStructure, outputResultArrayPath, err, replaceInput, inverse); + return ThresholdValue(comparisonValue, dataStructure, outputResultArrayPath, err, replaceInput, inverse, trueValue, falseValue); case DataType::uint64: - return ThresholdValue(comparisonValue, dataStructure, outputResultArrayPath, err, replaceInput, inverse); + return ThresholdValue(comparisonValue, dataStructure, outputResultArrayPath, err, replaceInput, inverse, trueValue, falseValue); case DataType::float32: - return ThresholdValue(comparisonValue, dataStructure, outputResultArrayPath, err, replaceInput, inverse); + return ThresholdValue(comparisonValue, dataStructure, outputResultArrayPath, err, replaceInput, inverse, trueValue, falseValue); case DataType::float64: - return ThresholdValue(comparisonValue, dataStructure, outputResultArrayPath, err, replaceInput, inverse); + return ThresholdValue(comparisonValue, dataStructure, outputResultArrayPath, err, replaceInput, inverse, trueValue, falseValue); case DataType::boolean: [[fallthrough]]; default: - return ThresholdValue(comparisonValue, dataStructure, outputResultArrayPath, err, replaceInput, inverse); + return ThresholdValue(comparisonValue, dataStructure, outputResultArrayPath, err, replaceInput, inverse, trueValue, falseValue); } } template -void ThresholdSet(std::shared_ptr& inputComparisonSet, DataStructure& dataStructure, DataPath& outputResultArrayPath, int32_t& err, bool replaceInput, bool inverse) +void ThresholdSet(std::shared_ptr& inputComparisonSet, DataStructure& dataStructure, DataPath& outputResultArrayPath, int32_t& err, bool replaceInput, bool inverse, T trueValue, + T falseValue) { if(nullptr == inputComparisonSet) { @@ -265,7 +268,7 @@ void ThresholdSet(std::shared_ptr& inputComparisonSet, DataSt // Get the total number of tuples, create and initialize an array with FALSE to use for these results size_t totalTuples = outputResultArray.getNumberOfTuples(); - std::vector tempResultVector(totalTuples, false); + std::vector tempResultVector(totalTuples, falseValue); T firstValueFound = 0; @@ -275,13 +278,13 @@ void ThresholdSet(std::shared_ptr& inputComparisonSet, DataSt if(std::dynamic_pointer_cast(threshold)) { std::shared_ptr comparisonSet = std::dynamic_pointer_cast(threshold); - ThresholdSet(comparisonSet, dataStructure, outputResultArrayPath, err, !firstValueFound, false); + ThresholdSet(comparisonSet, dataStructure, outputResultArrayPath, err, !firstValueFound, false, trueValue, falseValue); firstValueFound = true; } else if(std::dynamic_pointer_cast(threshold)) { std::shared_ptr comparisonValue = std::dynamic_pointer_cast(threshold); - ThresholdValue(comparisonValue, dataStructure, outputResultArrayPath, err, !firstValueFound, false); + ThresholdValue(comparisonValue, dataStructure, outputResultArrayPath, err, !firstValueFound, false, trueValue, falseValue); firstValueFound = true; } } @@ -301,42 +304,71 @@ void ThresholdSet(std::shared_ptr& inputComparisonSet, DataSt else { // insert into current threshold - InsertThreshold(totalTuples, outputResultArray, inputComparisonSet->getUnionOperator(), tempResultVector, inverse); + InsertThreshold(totalTuples, outputResultArray, inputComparisonSet->getUnionOperator(), tempResultVector, inverse, trueValue, falseValue); } } void ThresholdSet(std::shared_ptr& inputComparisonSet, DataStructure& dataStructure, DataPath& outputResultArrayPath, DataType maskArrayType, int32_t& err, bool replaceInput, - bool inverse) + bool inverse, float64 trueValue, float64 falseValue) { switch(maskArrayType) { case DataType::int8: - return ThresholdSet(inputComparisonSet, dataStructure, outputResultArrayPath, err, replaceInput, inverse); + return ThresholdSet(inputComparisonSet, dataStructure, outputResultArrayPath, err, replaceInput, inverse, trueValue, falseValue); case DataType::int16: - return ThresholdSet(inputComparisonSet, dataStructure, outputResultArrayPath, err, replaceInput, inverse); + return ThresholdSet(inputComparisonSet, dataStructure, outputResultArrayPath, err, replaceInput, inverse, trueValue, falseValue); case DataType::int32: - return ThresholdSet(inputComparisonSet, dataStructure, outputResultArrayPath, err, replaceInput, inverse); + return ThresholdSet(inputComparisonSet, dataStructure, outputResultArrayPath, err, replaceInput, inverse, trueValue, falseValue); case DataType::int64: - return ThresholdSet(inputComparisonSet, dataStructure, outputResultArrayPath, err, replaceInput, inverse); + return ThresholdSet(inputComparisonSet, dataStructure, outputResultArrayPath, err, replaceInput, inverse, trueValue, falseValue); case DataType::uint8: - return ThresholdSet(inputComparisonSet, dataStructure, outputResultArrayPath, err, replaceInput, inverse); + return ThresholdSet(inputComparisonSet, dataStructure, outputResultArrayPath, err, replaceInput, inverse, trueValue, falseValue); case DataType::uint16: - return ThresholdSet(inputComparisonSet, dataStructure, outputResultArrayPath, err, replaceInput, inverse); + return ThresholdSet(inputComparisonSet, dataStructure, outputResultArrayPath, err, replaceInput, inverse, trueValue, falseValue); case DataType::uint32: - return ThresholdSet(inputComparisonSet, dataStructure, outputResultArrayPath, err, replaceInput, inverse); + return ThresholdSet(inputComparisonSet, dataStructure, outputResultArrayPath, err, replaceInput, inverse, trueValue, falseValue); case DataType::uint64: - return ThresholdSet(inputComparisonSet, dataStructure, outputResultArrayPath, err, replaceInput, inverse); + return ThresholdSet(inputComparisonSet, dataStructure, outputResultArrayPath, err, replaceInput, inverse, trueValue, falseValue); case DataType::float32: - return ThresholdSet(inputComparisonSet, dataStructure, outputResultArrayPath, err, replaceInput, inverse); + return ThresholdSet(inputComparisonSet, dataStructure, outputResultArrayPath, err, replaceInput, inverse, trueValue, falseValue); case DataType::float64: - return ThresholdSet(inputComparisonSet, dataStructure, outputResultArrayPath, err, replaceInput, inverse); + return ThresholdSet(inputComparisonSet, dataStructure, outputResultArrayPath, err, replaceInput, inverse, trueValue, falseValue); case DataType::boolean: [[fallthrough]]; default: - return ThresholdSet(inputComparisonSet, dataStructure, outputResultArrayPath, err, replaceInput, inverse); + return ThresholdSet(inputComparisonSet, dataStructure, outputResultArrayPath, err, replaceInput, inverse, trueValue, falseValue); } } +struct CheckCustomValueInBounds +{ + template + Result<> operator()(float64 customValue) + { + float64 minValue; + float64 maxValue; + if constexpr(std::is_floating_point_v) + { + // Floating Point Types + minValue = static_cast(-std::numeric_limits::max()); + maxValue = static_cast(std::numeric_limits::max()); + } + else + { + // Everything Else + minValue = static_cast(std::numeric_limits::min()); + maxValue = static_cast(std::numeric_limits::max()); + } + + if(customValue < minValue || customValue > maxValue) + { + return MakeErrorResult(-100, "Custom value is outside the bounds of the chosen data type!"); + } + + return {}; + } +}; + } // namespace // ----------------------------------------------------------------------------- @@ -377,8 +409,16 @@ Parameters MultiThresholdObjects::parameters() const params.insertSeparator(Parameters::Separator{"Input Parameters"}); params.insert( std::make_unique(k_ArrayThresholds_Key, "Data Thresholds", "DataArray thresholds to mask", ArrayThresholdSet{}, ArrayThresholdsParameter::AllowedComponentShapes{{1}})); - params.insert(std::make_unique(k_CreatedDataPath_Key, "Mask Array", "DataPath to the created Mask Array", "Mask")); 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)); + params.insertLinkableParameter(std::make_unique(k_UseCustomFalseValue, "Use Custom FALSE Value", "Specifies whether to output a custom FALSE value (the default value is 0)", false)); + params.insert(std::make_unique>(k_CustomFalseValue, "Custom FALSE Value", "This is the custom FALSE value that will be output to the mask array", 0.0)); + params.insert(std::make_unique(k_CreatedDataPath_Key, "Mask Array", "DataPath to the created Mask Array", "Mask")); + + params.linkParameters(k_UseCustomTrueValue, k_CustomTrueValue, true); + params.linkParameters(k_UseCustomFalseValue, k_CustomFalseValue, true); + return params; } @@ -394,6 +434,10 @@ IFilter::PreflightResult MultiThresholdObjects::preflightImpl(const DataStructur auto thresholdsObject = args.value(k_ArrayThresholds_Key); auto maskArrayName = args.value(k_CreatedDataPath_Key); auto maskArrayType = args.value(k_CreatedMaskType_Key); + auto useCustomTrueValue = args.value(k_UseCustomTrueValue); + auto useCustomFalseValue = args.value(k_UseCustomFalseValue); + auto customTrueValue = args.value::ValueType>(k_CustomTrueValue); + auto customFalseValue = args.value::ValueType>(k_CustomFalseValue); auto thresholdPaths = thresholdsObject.getRequiredPaths(); // If the paths are empty just return now. @@ -407,7 +451,7 @@ IFilter::PreflightResult MultiThresholdObjects::preflightImpl(const DataStructur if(data.getData(path) == nullptr) { auto errorMessage = fmt::format("Could not find DataArray at path {}.", path.toString()); - return {nonstd::make_unexpected(std::vector{Error{k_PathNotFoundError, errorMessage}})}; + return {nonstd::make_unexpected(std::vector{Error{to_underlying(ErrorCodes::PathNotFoundError), errorMessage}})}; } } @@ -418,7 +462,7 @@ IFilter::PreflightResult MultiThresholdObjects::preflightImpl(const DataStructur if(currentDataArray != nullptr && currentDataArray->getNumberOfComponents() != 1) { 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{-4001, errorMessage}})}; + return {nonstd::make_unexpected(std::vector{Error{to_underlying(ErrorCodes::NonScalarArrayFound), errorMessage}})}; } } @@ -434,7 +478,40 @@ IFilter::PreflightResult MultiThresholdObjects::preflightImpl(const DataStructur { 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{-4002, errorMessage}})}; + return {nonstd::make_unexpected(std::vector{Error{to_underlying(ErrorCodes::UnequalTuples), errorMessage}})}; + } + } + + 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."}})}; + } + + if(useCustomFalseValue) + { + return {nonstd::make_unexpected(std::vector{Error{to_underlying(ErrorCodes::CustomFalseWithBoolean), "Cannot use custom FALSE value with a boolean Mask Type."}})}; + } + } + + if(useCustomTrueValue) + { + Result<> result = ExecuteDataFunction(CheckCustomValueInBounds{}, maskArrayType, customTrueValue); + 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}})}; + } + } + + if(useCustomFalseValue) + { + Result<> result = ExecuteDataFunction(CheckCustomValueInBounds{}, maskArrayType, customFalseValue); + 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}})}; } } @@ -455,6 +532,13 @@ Result<> MultiThresholdObjects::executeImpl(DataStructure& dataStructure, const auto thresholdsObject = args.value(k_ArrayThresholds_Key); auto maskArrayName = args.value(k_CreatedDataPath_Key); auto maskArrayType = args.value(k_CreatedMaskType_Key); + auto useCustomTrueValue = args.value(k_UseCustomTrueValue); + auto useCustomFalseValue = args.value(k_UseCustomFalseValue); + auto customTrueValue = args.value::ValueType>(k_CustomTrueValue); + auto customFalseValue = args.value::ValueType>(k_CustomFalseValue); + + float64 trueValue = useCustomTrueValue ? customTrueValue : 1.0; + float64 falseValue = useCustomFalseValue ? customFalseValue : 0.0; bool firstValueFound = false; DataPath maskArrayPath = (*thresholdsObject.getRequiredPaths().begin()).getParent().createChildPath(maskArrayName); @@ -465,13 +549,13 @@ Result<> MultiThresholdObjects::executeImpl(DataStructure& dataStructure, const if(std::dynamic_pointer_cast(threshold)) { std::shared_ptr comparisonSet = std::dynamic_pointer_cast(threshold); - ThresholdSet(comparisonSet, dataStructure, maskArrayPath, maskArrayType, err, !firstValueFound, thresholdsObject.isInverted()); + ThresholdSet(comparisonSet, dataStructure, maskArrayPath, maskArrayType, err, !firstValueFound, thresholdsObject.isInverted(), trueValue, falseValue); firstValueFound = true; } else if(std::dynamic_pointer_cast(threshold)) { std::shared_ptr comparisonValue = std::dynamic_pointer_cast(threshold); - ThresholdValue(comparisonValue, dataStructure, maskArrayPath, maskArrayType, err, !firstValueFound, thresholdsObject.isInverted()); + ThresholdValue(comparisonValue, dataStructure, maskArrayPath, maskArrayType, err, !firstValueFound, thresholdsObject.isInverted(), trueValue, falseValue); firstValueFound = true; } } diff --git a/src/Plugins/ComplexCore/src/ComplexCore/Filters/MultiThresholdObjects.hpp b/src/Plugins/ComplexCore/src/ComplexCore/Filters/MultiThresholdObjects.hpp index f04dbd9cb1..06f4a53232 100644 --- a/src/Plugins/ComplexCore/src/ComplexCore/Filters/MultiThresholdObjects.hpp +++ b/src/Plugins/ComplexCore/src/ComplexCore/Filters/MultiThresholdObjects.hpp @@ -21,9 +21,24 @@ class COMPLEXCORE_EXPORT MultiThresholdObjects : public IFilter MultiThresholdObjects& operator=(MultiThresholdObjects&&) noexcept = delete; static inline constexpr StringLiteral k_ArrayThresholds_Key = "array_thresholds"; + static inline constexpr StringLiteral k_UseCustomTrueValue = "use_custom_true_value"; + static inline constexpr StringLiteral k_UseCustomFalseValue = "use_custom_false_value"; + static inline constexpr StringLiteral k_CustomTrueValue = "custom_true_value"; + static inline constexpr StringLiteral k_CustomFalseValue = "custom_false_value"; static inline constexpr StringLiteral k_CreatedDataPath_Key = "created_data_path"; static inline constexpr StringLiteral k_CreatedMaskType_Key = "created_mask_type"; + enum ErrorCodes : int64 + { + PathNotFoundError = -178, + NonScalarArrayFound = -4001, + UnequalTuples = -4002, + CustomTrueWithBoolean = -4003, + CustomFalseWithBoolean = -4004, + CustomTrueOutOfBounds = -4005, + CustomFalseOutOfBounds = -4006 + }; + /** * @brief * @return std::string diff --git a/src/Plugins/ComplexCore/test/MultiThresholdObjectsTest.cpp b/src/Plugins/ComplexCore/test/MultiThresholdObjectsTest.cpp index e67c040b01..9b4711573a 100644 --- a/src/Plugins/ComplexCore/test/MultiThresholdObjectsTest.cpp +++ b/src/Plugins/ComplexCore/test/MultiThresholdObjectsTest.cpp @@ -56,6 +56,27 @@ DataStructure CreateTestDataStructure() } return dataStructure; } + +template +float64 GetOutOfBoundsMinimumValue() +{ + if constexpr(std::is_unsigned_v) + { + return -1.0; + } + else if constexpr(std::is_floating_point_v) + { + return static_cast(-std::numeric_limits::max()) * 2; + } + + return static_cast(std::numeric_limits::min()) * 2; +} + +template +float64 GetOutOfBoundsMaximumValue() +{ + return static_cast(std::numeric_limits::max()) * 2; +} } // namespace TEST_CASE("ComplexCore::MultiThresholdObjects: Valid Execution", "[ComplexCore][MultiThresholdObjects]") @@ -145,6 +166,56 @@ TEST_CASE("ComplexCore::MultiThresholdObjects: Valid Execution", "[ComplexCore][ } } +TEMPLATE_TEST_CASE("ComplexCore::MultiThresholdObjects: Valid Execution - Custom Values", "[ComplexCore][MultiThresholdObjects]", int8, uint8, int16, uint16, int32, uint32, int64, uint64, float32, + float64) +{ + MultiThresholdObjects 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(MultiThresholdObjects::k_ArrayThresholds_Key, std::make_any(thresholdSet)); + args.insertOrAssign(MultiThresholdObjects::k_CreatedDataPath_Key, std::make_any(k_ThresholdArrayName)); + args.insertOrAssign(MultiThresholdObjects::k_UseCustomTrueValue, std::make_any(true)); + args.insertOrAssign(MultiThresholdObjects::k_CustomTrueValue, std::make_any(trueValue)); + args.insertOrAssign(MultiThresholdObjects::k_UseCustomFalseValue, std::make_any(true)); + args.insertOrAssign(MultiThresholdObjects::k_CustomFalseValue, std::make_any(falseValue)); + args.insertOrAssign(MultiThresholdObjects::k_CreatedMaskType_Key, std::make_any(GetDataType())); + + // Preflight the filter and check result + auto preflightResult = filter.preflight(dataStructure, args); + COMPLEX_RESULT_REQUIRE_VALID(preflightResult.outputActions) + + // Execute the filter and check the result + auto executeResult = filter.execute(dataStructure, args); + COMPLEX_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); + } + } +} + TEST_CASE("ComplexCore::MultiThresholdObjects: Invalid Execution", "[ComplexCore][MultiThresholdObjects]") { MultiThresholdObjects filter; @@ -207,6 +278,105 @@ TEST_CASE("ComplexCore::MultiThresholdObjects: Invalid Execution", "[ComplexCore COMPLEX_RESULT_REQUIRE_INVALID(executeResult.result) } +TEMPLATE_TEST_CASE("ComplexCore::MultiThresholdObjects: Invalid Execution - Out of Bounds Custom Values", "[ComplexCore][MultiThresholdObjects]", int8, uint8, int16, uint16, int32, uint32, int64, + uint64, float32) +{ + MultiThresholdObjects filter; + DataStructure dataStructure = CreateTestDataStructure(); + Arguments args; + + float64 trueValue; + float64 falseValue; + int32 code; + + SECTION("True Value < Minimum") + { + trueValue = GetOutOfBoundsMinimumValue(); + falseValue = 1; + code = MultiThresholdObjects::ErrorCodes::CustomTrueOutOfBounds; + } + + SECTION("False Value < Minimum") + { + trueValue = 1; + falseValue = GetOutOfBoundsMinimumValue(); + code = MultiThresholdObjects::ErrorCodes::CustomFalseOutOfBounds; + } + + SECTION("True Value > Maximum") + { + trueValue = GetOutOfBoundsMaximumValue(); + falseValue = 1; + code = MultiThresholdObjects::ErrorCodes::CustomTrueOutOfBounds; + } + + SECTION("False Value > Maximum") + { + trueValue = 1; + falseValue = GetOutOfBoundsMaximumValue(); + code = MultiThresholdObjects::ErrorCodes::CustomFalseOutOfBounds; + } + + ArrayThresholdSet thresholdSet; + auto threshold = std::make_shared(); + threshold->setArrayPath(k_TestArrayIntPath); + threshold->setComparisonType(ArrayThreshold::ComparisonType::GreaterThan); + threshold->setComparisonValue(15); + thresholdSet.setArrayThresholds({threshold}); + + args.insertOrAssign(MultiThresholdObjects::k_ArrayThresholds_Key, std::make_any(thresholdSet)); + args.insertOrAssign(MultiThresholdObjects::k_CreatedDataPath_Key, std::make_any(k_ThresholdArrayName)); + args.insertOrAssign(MultiThresholdObjects::k_UseCustomTrueValue, std::make_any(true)); + args.insertOrAssign(MultiThresholdObjects::k_CustomTrueValue, std::make_any(trueValue)); + args.insertOrAssign(MultiThresholdObjects::k_UseCustomFalseValue, std::make_any(true)); + args.insertOrAssign(MultiThresholdObjects::k_CustomFalseValue, std::make_any(falseValue)); + args.insertOrAssign(MultiThresholdObjects::k_CreatedMaskType_Key, std::make_any(GetDataType())); + + // Preflight the filter + auto preflightResult = filter.preflight(dataStructure, args); + COMPLEX_RESULT_REQUIRE_INVALID(preflightResult.outputActions); + REQUIRE(preflightResult.outputActions.errors().size() == 1); + REQUIRE(preflightResult.outputActions.errors()[0].code == code); +} + +TEST_CASE("ComplexCore::MultiThresholdObjects: Invalid Execution - Boolean Custom Values", "[ComplexCore][MultiThresholdObjects]") +{ + MultiThresholdObjects filter; + DataStructure dataStructure = CreateTestDataStructure(); + Arguments args; + + int32 code; + + SECTION("Custom True Value") + { + code = MultiThresholdObjects::ErrorCodes::CustomTrueWithBoolean; + args.insertOrAssign(MultiThresholdObjects::k_UseCustomTrueValue, std::make_any(true)); + } + + SECTION("Custom False Value") + { + code = MultiThresholdObjects::ErrorCodes::CustomFalseWithBoolean; + args.insertOrAssign(MultiThresholdObjects::k_UseCustomFalseValue, std::make_any(true)); + } + + ArrayThresholdSet thresholdSet; + auto threshold = std::make_shared(); + threshold->setArrayPath(k_TestArrayIntPath); + threshold->setComparisonType(ArrayThreshold::ComparisonType::GreaterThan); + threshold->setComparisonValue(15); + thresholdSet.setArrayThresholds({threshold}); + + args.insertOrAssign(MultiThresholdObjects::k_ArrayThresholds_Key, std::make_any(thresholdSet)); + args.insertOrAssign(MultiThresholdObjects::k_CreatedDataPath_Key, std::make_any(k_ThresholdArrayName)); + args.insertOrAssign(MultiThresholdObjects::k_CreatedMaskType_Key, std::make_any(DataType::boolean)); + + // Preflight the filter + auto preflightResult = filter.preflight(dataStructure, args); + COMPLEX_RESULT_REQUIRE_INVALID(preflightResult.outputActions); + REQUIRE(preflightResult.outputActions.errors().size() == 1); + REQUIRE(preflightResult.outputActions.errors()[0].code == code); +} + template void checkMaskValues(const DataStructure& dataStructure, const DataPath& thresholdArrayPath) {