Skip to content

Commit

Permalink
ENH: GenerateColorTable allows use of a mask array. (#747)
Browse files Browse the repository at this point in the history
* Mask values that have a value of FALSE will get the "Invalid Color" applied.

---------

Signed-off-by: Michael Jackson <[email protected]>
Co-authored-by: Nathan Young <[email protected]>
  • Loading branch information
imikejackson and nyoungbq authored Oct 20, 2023
1 parent ae787bc commit e8b0ae2
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 11 deletions.
3 changes: 3 additions & 0 deletions src/Plugins/ComplexCore/docs/GenerateColorTableFilter.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ using namespace complex;
namespace
{
// -----------------------------------------------------------------------------
usize FindRightBinIndex(float32 nValue, const std::vector<float32>& binPoints)
usize findRightBinIndex(float32 nValue, const std::vector<float32>& binPoints)
{
usize min = 0;
usize max = binPoints.size() - 1;
Expand All @@ -39,14 +39,17 @@ template <typename T>
class GenerateColorTableImpl
{
public:
GenerateColorTableImpl(const DataArray<T>& arrayPtr, const std::vector<float32>& binPoints, const std::vector<std::vector<float64>>& controlPoints, int numControlColors, UInt8Array& colorArray)
GenerateColorTableImpl(const DataArray<T>& arrayPtr, const std::vector<float32>& binPoints, const std::vector<std::vector<float64>>& controlPoints, int numControlColors, UInt8Array& colorArray,
const complex::IDataArray* goodVoxels, const std::vector<uint8>& invalidColor)
: m_ArrayPtr(arrayPtr)
, m_BinPoints(binPoints)
, m_NumControlColors(numControlColors)
, m_ControlPoints(controlPoints)
, 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++)
{
Expand All @@ -67,14 +70,35 @@ class GenerateColorTableImpl
GenerateColorTableImpl& operator=(const GenerateColorTableImpl&) = delete;
GenerateColorTableImpl& operator=(GenerateColorTableImpl&&) noexcept = delete;

template <typename K>
void convert(size_t start, size_t end) const
{
using MaskArrayType = DataArray<K>;
const MaskArrayType* maskArray = nullptr;
if(nullptr != m_GoodVoxels)
{
maskArray = dynamic_cast<const MaskArrayType*>(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<float32>(m_ArrayPtr[i] - m_ArrayMin)) / static_cast<float32>((m_ArrayMax - m_ArrayMin));

int rightBinIndex = FindRightBinIndex(nValue, m_BinPoints);
int rightBinIndex = findRightBinIndex(nValue, m_BinPoints);

int leftBinIndex = rightBinIndex - 1;
if(leftBinIndex < 0)
Expand Down Expand Up @@ -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);
Expand All @@ -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<bool>(range.min(), range.max());
}
else if(m_GoodVoxels->getDataType() == DataType::uint8)
{
convert<uint8>(range.min(), range.max());
}
}
else
{
convert<bool>(range.min(), range.max());
}
}

private:
Expand All @@ -126,14 +163,20 @@ class GenerateColorTableImpl
int m_NumControlColors;
const std::vector<std::vector<float64>>& m_ControlPoints;
UInt8Array& m_ColorArray;
const complex::IDataArray* m_GoodVoxels = nullptr;
const std::vector<uint8>& m_InvalidColor;
};

struct GenerateColorArrayFunctor
{
template <typename ScalarType>
void operator()(const DataPath& selectedArrayPath, const nlohmann::json& presetControlPoints, const DataPath& rgbArrayPath, DataStructure& dataStructure)
void operator()(DataStructure& dataStructure, const GenerateColorTableInputValues* inputValues)
{
const DataArray<ScalarType>& arrayPtr = dataStructure.getDataRefAs<DataArray<ScalarType>>(selectedArrayPath);

const nlohmann::json presetControlPoints = inputValues->SelectedPreset["RGBPoints"];
const DataPath rgbArrayPath = inputValues->RgbArrayPath;

const DataArray<ScalarType>& arrayPtr = dataStructure.getDataRefAs<DataArray<ScalarType>>(inputValues->SelectedDataArrayPath);
if(arrayPtr.getNumberOfTuples() <= 0)
{
return;
Expand Down Expand Up @@ -172,9 +215,15 @@ struct GenerateColorArrayFunctor

auto& colorArray = dataStructure.getDataRefAs<UInt8Array>(rgbArrayPath);

complex::IDataArray* goodVoxelsArray = nullptr;
if(inputValues->UseGoodVoxels)
{
goodVoxelsArray = dataStructure.getDataAs<IDataArray>(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
Expand All @@ -201,7 +250,6 @@ const std::atomic_bool& GenerateColorTable::getCancel()
Result<> GenerateColorTable::operator()()
{
const IDataArray& selectedIDataArray = m_DataStructure.getDataRefAs<IDataArray>(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 {};
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ struct COMPLEXCORE_EXPORT GenerateColorTableInputValues
nlohmann::json SelectedPreset;
DataPath SelectedDataArrayPath;
DataPath RgbArrayPath;
bool UseGoodVoxels;
DataPath GoodVoxelsArrayPath;
std::vector<uint8> InvalidColor;
};

class COMPLEXCORE_EXPORT GenerateColorTable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -55,6 +68,16 @@ Parameters GenerateColorTableFilter::parameters() const
params.insert(std::make_unique<ArraySelectionParameter>(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<BoolParameter>(k_UseGoodVoxels_Key, "Use Mask Array", "Whether to assign a black color to 'bad' Elements", false));
params.insert(std::make_unique<ArraySelectionParameter>(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<VectorUInt8Parameter>(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<std::string>{"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<DataObjectNameParameter>(
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", ""));
Expand All @@ -76,13 +99,45 @@ IFilter::PreflightResult GenerateColorTableFilter::preflightImpl(const DataStruc
auto pSelectedDataArrayPathValue = filterArgs.value<DataPath>(k_SelectedDataArrayPath_Key);
auto pRgbArrayPathValue = pSelectedDataArrayPathValue.getParent().createChildPath(filterArgs.value<std::string>(k_RgbArrayPath_Key));

auto pUseGoodVoxelsValue = filterArgs.value<bool>(k_UseGoodVoxels_Key);
auto pGoodVoxelsArrayPathValue = filterArgs.value<DataPath>(k_GoodVoxelsPath_Key);

complex::Result<OutputActions> resultOutputActions;
std::vector<PreflightValue> preflightUpdatedValues;

const auto& dataArray = dataStructure.getDataRefAs<IDataArray>(pSelectedDataArrayPathValue);
auto createArrayAction = std::make_unique<CreateArrayAction>(DataType::uint8, dataArray.getTupleShape(), std::vector<usize>{3}, pRgbArrayPathValue);
resultOutputActions.value().appendAction(std::move(createArrayAction));

std::vector<DataPath> dataPaths;
dataPaths.push_back(pSelectedDataArrayPathValue);

// Validate the GoodVoxels/Mask Array combination
DataPath goodVoxelsPath;
if(pUseGoodVoxelsValue)
{
goodVoxelsPath = filterArgs.value<DataPath>(k_GoodVoxelsPath_Key);

const complex::IDataArray* goodVoxelsArray = dataStructure.getDataAs<IDataArray>(goodVoxelsPath);
if(nullptr == goodVoxelsArray)
{
return {nonstd::make_unexpected(std::vector<Error>{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>{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<OutputActions>(-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)};
}

Expand All @@ -95,6 +150,9 @@ Result<> GenerateColorTableFilter::executeImpl(DataStructure& dataStructure, con
inputValues.SelectedPreset = filterArgs.value<nlohmann::json>(k_SelectedPreset_Key);
inputValues.SelectedDataArrayPath = filterArgs.value<DataPath>(k_SelectedDataArrayPath_Key);
inputValues.RgbArrayPath = inputValues.SelectedDataArrayPath.getParent().createChildPath(filterArgs.value<std::string>(k_RgbArrayPath_Key));
inputValues.UseGoodVoxels = filterArgs.value<bool>(k_UseGoodVoxels_Key);
inputValues.GoodVoxelsArrayPath = filterArgs.value<DataPath>(k_GoodVoxelsPath_Key);
inputValues.InvalidColor = filterArgs.value<std::vector<uint8>>(k_InvalidColorValue_Key);

return GenerateColorTable(dataStructure, messageHandler, shouldCancel, &inputValues)();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ std::string GenerateIPFColorsFilter::humanName() const
//------------------------------------------------------------------------------
std::vector<std::string> GenerateIPFColorsFilter::defaultTags() const
{
return {className(), "Processing", "Crystallography"};
return {className(), "Processing", "Crystallography", "Inverse Pole Figure", "Colors"};
}

//------------------------------------------------------------------------------
Expand Down

0 comments on commit e8b0ae2

Please sign in to comment.