From e8b0ae21d9bb4c2ac080e466bb3db62a37b08d68 Mon Sep 17 00:00:00 2001 From: Michael Jackson Date: Thu, 19 Oct 2023 21:38:48 -0400 Subject: [PATCH] ENH: GenerateColorTable allows use of a mask array. (#747) * Mask values that have a value of FALSE will get the "Invalid Color" applied. --------- Signed-off-by: Michael Jackson Co-authored-by: Nathan Young --- .../docs/GenerateColorTableFilter.md | 3 + .../Filters/Algorithms/GenerateColorTable.cpp | 68 ++++++++++++++++--- .../Filters/Algorithms/GenerateColorTable.hpp | 3 + .../Filters/GenerateColorTableFilter.cpp | 58 ++++++++++++++++ .../Filters/GenerateColorTableFilter.hpp | 3 + .../Filters/GenerateIPFColorsFilter.cpp | 2 +- 6 files changed, 126 insertions(+), 11 deletions(-) diff --git a/src/Plugins/ComplexCore/docs/GenerateColorTableFilter.md b/src/Plugins/ComplexCore/docs/GenerateColorTableFilter.md index b1278c60a1..67f14c62df 100644 --- a/src/Plugins/ComplexCore/docs/GenerateColorTableFilter.md +++ b/src/Plugins/ComplexCore/docs/GenerateColorTableFilter.md @@ -5,6 +5,9 @@ This **Filter** generates a color table array for a given 1-component input array. Each element of the input array is normalized and converted to a color based on where the value falls in the spectrum of the selected color preset. +The user can apply an optional data mask and then set the RGB values (0-255) that will be used if the data mask has a FALSE +value. + % Auto generated parameter table will be inserted here ## Example Pipelines diff --git a/src/Plugins/ComplexCore/src/ComplexCore/Filters/Algorithms/GenerateColorTable.cpp b/src/Plugins/ComplexCore/src/ComplexCore/Filters/Algorithms/GenerateColorTable.cpp index 02a3cec901..dc65af4afd 100644 --- a/src/Plugins/ComplexCore/src/ComplexCore/Filters/Algorithms/GenerateColorTable.cpp +++ b/src/Plugins/ComplexCore/src/ComplexCore/Filters/Algorithms/GenerateColorTable.cpp @@ -12,7 +12,7 @@ using namespace complex; namespace { // ----------------------------------------------------------------------------- -usize FindRightBinIndex(float32 nValue, const std::vector& binPoints) +usize findRightBinIndex(float32 nValue, const std::vector& binPoints) { usize min = 0; usize max = binPoints.size() - 1; @@ -39,7 +39,8 @@ template class GenerateColorTableImpl { public: - GenerateColorTableImpl(const DataArray& arrayPtr, const std::vector& binPoints, const std::vector>& controlPoints, int numControlColors, UInt8Array& colorArray) + GenerateColorTableImpl(const DataArray& arrayPtr, const std::vector& binPoints, const std::vector>& controlPoints, int numControlColors, UInt8Array& colorArray, + const complex::IDataArray* goodVoxels, const std::vector& invalidColor) : m_ArrayPtr(arrayPtr) , m_BinPoints(binPoints) , m_NumControlColors(numControlColors) @@ -47,6 +48,8 @@ class GenerateColorTableImpl , m_ColorArray(colorArray) , m_ArrayMin(arrayPtr[0]) , m_ArrayMax(arrayPtr[0]) + , m_GoodVoxels(goodVoxels) + , m_InvalidColor(invalidColor) { for(int i = 1; i < arrayPtr.getNumberOfTuples(); i++) { @@ -67,14 +70,35 @@ class GenerateColorTableImpl GenerateColorTableImpl& operator=(const GenerateColorTableImpl&) = delete; GenerateColorTableImpl& operator=(GenerateColorTableImpl&&) noexcept = delete; + template void convert(size_t start, size_t end) const { + using MaskArrayType = DataArray; + const MaskArrayType* maskArray = nullptr; + if(nullptr != m_GoodVoxels) + { + maskArray = dynamic_cast(m_GoodVoxels); + } + auto& colorArrayDS = m_ColorArray.getDataStoreRef(); + for(size_t i = start; i < end; i++) { + // Make sure we are using a valid voxel based on the "goodVoxels" arrays + if(nullptr != maskArray) + { + if(!(*maskArray)[i]) + { + colorArrayDS.setComponent(i, 0, m_InvalidColor[0]); + colorArrayDS.setComponent(i, 1, m_InvalidColor[1]); + colorArrayDS.setComponent(i, 2, m_InvalidColor[2]); + continue; + } + } + // Normalize value const float32 nValue = (static_cast(m_ArrayPtr[i] - m_ArrayMin)) / static_cast((m_ArrayMax - m_ArrayMin)); - int rightBinIndex = FindRightBinIndex(nValue, m_BinPoints); + int rightBinIndex = findRightBinIndex(nValue, m_BinPoints); int leftBinIndex = rightBinIndex - 1; if(leftBinIndex < 0) @@ -106,7 +130,6 @@ class GenerateColorTableImpl const unsigned char greenVal = (m_ControlPoints[leftBinIndex][2] * (1.0 - currFraction) + m_ControlPoints[rightBinIndex][2] * currFraction) * 255; const unsigned char blueVal = (m_ControlPoints[leftBinIndex][3] * (1.0 - currFraction) + m_ControlPoints[rightBinIndex][3] * currFraction) * 255; - auto& colorArrayDS = m_ColorArray.getDataStoreRef(); colorArrayDS.setComponent(i, 0, redVal); colorArrayDS.setComponent(i, 1, greenVal); colorArrayDS.setComponent(i, 2, blueVal); @@ -115,7 +138,21 @@ class GenerateColorTableImpl void operator()(const Range& range) const { - convert(range.min(), range.max()); + if(m_GoodVoxels != nullptr) + { + if(m_GoodVoxels->getDataType() == DataType::boolean) + { + convert(range.min(), range.max()); + } + else if(m_GoodVoxels->getDataType() == DataType::uint8) + { + convert(range.min(), range.max()); + } + } + else + { + convert(range.min(), range.max()); + } } private: @@ -126,14 +163,20 @@ class GenerateColorTableImpl int m_NumControlColors; const std::vector>& m_ControlPoints; UInt8Array& m_ColorArray; + const complex::IDataArray* m_GoodVoxels = nullptr; + const std::vector& m_InvalidColor; }; struct GenerateColorArrayFunctor { template - void operator()(const DataPath& selectedArrayPath, const nlohmann::json& presetControlPoints, const DataPath& rgbArrayPath, DataStructure& dataStructure) + void operator()(DataStructure& dataStructure, const GenerateColorTableInputValues* inputValues) { - const DataArray& arrayPtr = dataStructure.getDataRefAs>(selectedArrayPath); + + const nlohmann::json presetControlPoints = inputValues->SelectedPreset["RGBPoints"]; + const DataPath rgbArrayPath = inputValues->RgbArrayPath; + + const DataArray& arrayPtr = dataStructure.getDataRefAs>(inputValues->SelectedDataArrayPath); if(arrayPtr.getNumberOfTuples() <= 0) { return; @@ -172,9 +215,15 @@ struct GenerateColorArrayFunctor auto& colorArray = dataStructure.getDataRefAs(rgbArrayPath); + complex::IDataArray* goodVoxelsArray = nullptr; + if(inputValues->UseGoodVoxels) + { + goodVoxelsArray = dataStructure.getDataAs(inputValues->GoodVoxelsArrayPath); + } + ParallelDataAlgorithm dataAlg; dataAlg.setRange(0, arrayPtr.getNumberOfTuples()); - dataAlg.execute(GenerateColorTableImpl(arrayPtr, binPoints, controlPoints, numControlColors, colorArray)); + dataAlg.execute(GenerateColorTableImpl(arrayPtr, binPoints, controlPoints, numControlColors, colorArray, goodVoxelsArray, inputValues->InvalidColor)); } }; } // namespace @@ -201,7 +250,6 @@ const std::atomic_bool& GenerateColorTable::getCancel() Result<> GenerateColorTable::operator()() { const IDataArray& selectedIDataArray = m_DataStructure.getDataRefAs(m_InputValues->SelectedDataArrayPath); - ExecuteDataFunction(GenerateColorArrayFunctor{}, selectedIDataArray.getDataType(), m_InputValues->SelectedDataArrayPath, m_InputValues->SelectedPreset["RGBPoints"], m_InputValues->RgbArrayPath, - m_DataStructure); + ExecuteDataFunction(GenerateColorArrayFunctor{}, selectedIDataArray.getDataType(), m_DataStructure, m_InputValues); return {}; } diff --git a/src/Plugins/ComplexCore/src/ComplexCore/Filters/Algorithms/GenerateColorTable.hpp b/src/Plugins/ComplexCore/src/ComplexCore/Filters/Algorithms/GenerateColorTable.hpp index ef43dcefcd..f363100c30 100644 --- a/src/Plugins/ComplexCore/src/ComplexCore/Filters/Algorithms/GenerateColorTable.hpp +++ b/src/Plugins/ComplexCore/src/ComplexCore/Filters/Algorithms/GenerateColorTable.hpp @@ -17,6 +17,9 @@ struct COMPLEXCORE_EXPORT GenerateColorTableInputValues nlohmann::json SelectedPreset; DataPath SelectedDataArrayPath; DataPath RgbArrayPath; + bool UseGoodVoxels; + DataPath GoodVoxelsArrayPath; + std::vector InvalidColor; }; class COMPLEXCORE_EXPORT GenerateColorTable diff --git a/src/Plugins/ComplexCore/src/ComplexCore/Filters/GenerateColorTableFilter.cpp b/src/Plugins/ComplexCore/src/ComplexCore/Filters/GenerateColorTableFilter.cpp index 4dea466818..f8a4e92d94 100644 --- a/src/Plugins/ComplexCore/src/ComplexCore/Filters/GenerateColorTableFilter.cpp +++ b/src/Plugins/ComplexCore/src/ComplexCore/Filters/GenerateColorTableFilter.cpp @@ -2,14 +2,27 @@ #include "Algorithms/GenerateColorTable.hpp" +#include "complex/DataStructure/DataArray.hpp" #include "complex/DataStructure/DataPath.hpp" #include "complex/DataStructure/IDataArray.hpp" #include "complex/Filter/Actions/CreateArrayAction.hpp" #include "complex/Parameters/ArraySelectionParameter.hpp" +#include "complex/Parameters/BoolParameter.hpp" #include "complex/Parameters/DataObjectNameParameter.hpp" #include "complex/Parameters/GenerateColorTableParameter.hpp" +#include "complex/Parameters/VectorParameter.hpp" using namespace complex; +namespace +{ + +using GoodVoxelsArrayType = BoolArray; + +inline constexpr int32 k_MissingGeomError = -72440; +inline constexpr int32 k_IncorrectInputArray = -72441; +inline constexpr int32 k_MissingInputArray = -72442; +inline constexpr int32 k_MissingOrIncorrectGoodVoxelsArray = -72443; +} // namespace namespace complex { @@ -55,6 +68,16 @@ Parameters GenerateColorTableFilter::parameters() const params.insert(std::make_unique(k_SelectedDataArrayPath_Key, "Data Array", "The complete path to the data array from which to create the rgb array by applying the selected preset color scheme", DataPath{}, complex::GetAllDataTypes(), ArraySelectionParameter::AllowedComponentShapes{{1}})); + + params.insertSeparator(Parameters::Separator{"Optional Data Mask"}); + params.insertLinkableParameter(std::make_unique(k_UseGoodVoxels_Key, "Use Mask Array", "Whether to assign a black color to 'bad' Elements", false)); + params.insert(std::make_unique(k_GoodVoxelsPath_Key, "Mask", "Path to the data array used to define Elements as good or bad.", DataPath(), + ArraySelectionParameter::AllowedTypes{DataType::boolean, DataType::uint8}, ArraySelectionParameter::AllowedComponentShapes{{1}})); + params.insert(std::make_unique(k_InvalidColorValue_Key, "Masked Voxel Color (RGB)", "The color to assign to voxels that have a mask value of FALSE", + VectorUInt8Parameter::ValueType{0, 0, 0}, std::vector{"Red", "Green", "Blue"})); + // Associate the Linkable Parameter(s) to the children parameters that they control + params.linkParameters(k_UseGoodVoxels_Key, k_GoodVoxelsPath_Key, true); + params.insertSeparator({"Created Data Objects"}); params.insert(std::make_unique( k_RgbArrayPath_Key, "Output RGB Array", "The rgb array created by normalizing each element of the input array and converting to a color based on the selected preset color scheme", "")); @@ -76,6 +99,9 @@ IFilter::PreflightResult GenerateColorTableFilter::preflightImpl(const DataStruc auto pSelectedDataArrayPathValue = filterArgs.value(k_SelectedDataArrayPath_Key); auto pRgbArrayPathValue = pSelectedDataArrayPathValue.getParent().createChildPath(filterArgs.value(k_RgbArrayPath_Key)); + auto pUseGoodVoxelsValue = filterArgs.value(k_UseGoodVoxels_Key); + auto pGoodVoxelsArrayPathValue = filterArgs.value(k_GoodVoxelsPath_Key); + complex::Result resultOutputActions; std::vector preflightUpdatedValues; @@ -83,6 +109,35 @@ IFilter::PreflightResult GenerateColorTableFilter::preflightImpl(const DataStruc auto createArrayAction = std::make_unique(DataType::uint8, dataArray.getTupleShape(), std::vector{3}, pRgbArrayPathValue); resultOutputActions.value().appendAction(std::move(createArrayAction)); + std::vector dataPaths; + dataPaths.push_back(pSelectedDataArrayPathValue); + + // Validate the GoodVoxels/Mask Array combination + DataPath goodVoxelsPath; + if(pUseGoodVoxelsValue) + { + goodVoxelsPath = filterArgs.value(k_GoodVoxelsPath_Key); + + const complex::IDataArray* goodVoxelsArray = dataStructure.getDataAs(goodVoxelsPath); + if(nullptr == goodVoxelsArray) + { + return {nonstd::make_unexpected(std::vector{Error{k_MissingOrIncorrectGoodVoxelsArray, fmt::format("Mask array is not located at path: '{}'", goodVoxelsPath.toString())}})}; + } + + if(goodVoxelsArray->getDataType() != DataType::boolean && goodVoxelsArray->getDataType() != DataType::uint8) + { + return {nonstd::make_unexpected( + std::vector{Error{k_MissingOrIncorrectGoodVoxelsArray, fmt::format("Mask array at path '{}' is not of the correct type. It must be Bool or UInt8", goodVoxelsPath.toString())}})}; + } + dataPaths.push_back(goodVoxelsPath); + } + + auto tupleValidityCheck = dataStructure.validateNumberOfTuples(dataPaths); + if(!tupleValidityCheck) + { + return {MakeErrorResult(-651, fmt::format("The following DataArrays all must have equal number of tuples but this was not satisfied.\n{}", tupleValidityCheck.error()))}; + } + return {std::move(resultOutputActions), std::move(preflightUpdatedValues)}; } @@ -95,6 +150,9 @@ Result<> GenerateColorTableFilter::executeImpl(DataStructure& dataStructure, con inputValues.SelectedPreset = filterArgs.value(k_SelectedPreset_Key); inputValues.SelectedDataArrayPath = filterArgs.value(k_SelectedDataArrayPath_Key); inputValues.RgbArrayPath = inputValues.SelectedDataArrayPath.getParent().createChildPath(filterArgs.value(k_RgbArrayPath_Key)); + inputValues.UseGoodVoxels = filterArgs.value(k_UseGoodVoxels_Key); + inputValues.GoodVoxelsArrayPath = filterArgs.value(k_GoodVoxelsPath_Key); + inputValues.InvalidColor = filterArgs.value>(k_InvalidColorValue_Key); return GenerateColorTable(dataStructure, messageHandler, shouldCancel, &inputValues)(); } diff --git a/src/Plugins/ComplexCore/src/ComplexCore/Filters/GenerateColorTableFilter.hpp b/src/Plugins/ComplexCore/src/ComplexCore/Filters/GenerateColorTableFilter.hpp index d3cc2cef82..9923ea64c2 100644 --- a/src/Plugins/ComplexCore/src/ComplexCore/Filters/GenerateColorTableFilter.hpp +++ b/src/Plugins/ComplexCore/src/ComplexCore/Filters/GenerateColorTableFilter.hpp @@ -29,6 +29,9 @@ class COMPLEXCORE_EXPORT GenerateColorTableFilter : public IFilter static inline constexpr StringLiteral k_SelectedPreset_Key = "selected_preset"; static inline constexpr StringLiteral k_SelectedDataArrayPath_Key = "selected_data_array_path"; static inline constexpr StringLiteral k_RgbArrayPath_Key = "output_rgb_array_name"; + static inline constexpr StringLiteral k_UseGoodVoxels_Key = "use_good_voxels"; + static inline constexpr StringLiteral k_GoodVoxelsPath_Key = "good_voxels_array_path"; + static inline constexpr StringLiteral k_InvalidColorValue_Key = "invalid_color_value"; /** * @brief Returns the name of the filter. diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/GenerateIPFColorsFilter.cpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/GenerateIPFColorsFilter.cpp index 8e78ba1fa6..6e5658ca9b 100644 --- a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/GenerateIPFColorsFilter.cpp +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/GenerateIPFColorsFilter.cpp @@ -52,7 +52,7 @@ std::string GenerateIPFColorsFilter::humanName() const //------------------------------------------------------------------------------ std::vector GenerateIPFColorsFilter::defaultTags() const { - return {className(), "Processing", "Crystallography"}; + return {className(), "Processing", "Crystallography", "Inverse Pole Figure", "Colors"}; } //------------------------------------------------------------------------------