Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 21 additions & 5 deletions src/Plugins/SimplnxCore/docs/ErodeDilateBadDataFilter.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,17 @@ Cell neighboring a *bad* **Cell** will be changed to *0*.

If the *bad* data is *eroded*, the Filter shrinks the
bad data by one **Cell** in an iterative sequence for a user defined number of iterations. During the *erode* process
the *Feature Id* of the *bad* **Cell** is changed from *0* to the *Feature Id* of the majority of its neighbors. If
there is a tie between two *Feature Ids*, then one of the *Feature Ids*, chosen randomly, will be assigned to the *bad*
**Cell**.
the *Feature Id* of the *bad* **Cell** is changed from *0* to the *Feature Id* of the majority of its neighbors.

Ties are broken deterministically, not randomly. The Filter visits the six face neighbors in the fixed order
*-Z, -Y, -X, +X, +Y, +Z*, and a later neighbor must have a strictly greater count than the current leader to replace it.
When two or more *Feature Ids* are tied for the majority, the one belonging to the earliest neighbor in that scan order
is assigned. The same input therefore always produces the same output.

| Before Erosion | After Erosion |
|--------------------------------------|--------------------------------------|
| ![](Images/ErodeDilateBadData_1.png) | ![](Images/ErodeDilateBadData_3.png) |

`

Goals a user might be trying to accomplish with this Filter include:

- Remove small or thin regions of bad data by running a single (or two) iteration *erode* operation.
Expand All @@ -54,6 +55,21 @@ The *Operation* parameter selects which morphological operation to apply:
- **Dilate [0]**: Grows bad data regions by one **Cell** per iteration. Any **Cell** neighboring a bad **Cell** has its *Feature Id* changed to 0.
- **Erode [1]**: Shrinks bad data regions by one **Cell** per iteration. Each bad **Cell** is assigned the *Feature Id* of the majority of its neighbors.

### Direction Restrictions

The *X Direction*, *Y Direction*, and *Z Direction* parameters control which of the six face neighbors participate. With
all three enabled the Filter uses all six face neighbors (*-Z, -Y, -X, +X, +Y, +Z*); disabling *Z Direction*, for
example, restricts the operation to the four in-plane neighbors so that bad data grows or shrinks only within each XY
slice.

### Preflight Errors

The Filter refuses to run in two cases:

- **-14601**: all three of *X Direction*, *Y Direction*, and *Z Direction* are disabled. At least one direction is
required, otherwise there are no neighbors to erode or dilate across.
- **-14602**: the selected **Image Geometry** has a dimension of *0* **Cells**. All three dimensions must be non-zero.

## WARNING: Feature Data Will Become Invalid

By modifying the cell level data, any feature data that was previously computed will most likely be invalid at this point. Filters that compute feature level data should be rerun to ensure accurate final results from your pipeline.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,9 @@ class ErodeDilateBadDataTransferDataImpl
ErodeDilateBadDataTransferDataImpl() = delete;
ErodeDilateBadDataTransferDataImpl(const ErodeDilateBadDataTransferDataImpl&) = default;

ErodeDilateBadDataTransferDataImpl(ErodeDilateBadData* filterAlg, usize totalPoints, ChoicesParameter::ValueType operation, const Int32AbstractDataStore& featureIds,
const std::vector<int64>& neighbors, const std::shared_ptr<IDataArray>& dataArrayPtr, MessageHelper& messageHelper)
: m_FilterAlg(filterAlg)
, m_TotalPoints(totalPoints)
ErodeDilateBadDataTransferDataImpl(usize totalPoints, ChoicesParameter::ValueType operation, const Int32AbstractDataStore& featureIds, const std::vector<int64>& neighbors,
const std::shared_ptr<IDataArray>& dataArrayPtr, MessageHelper& messageHelper)
: m_TotalPoints(totalPoints)
, m_Operation(operation)
, m_Neighbors(neighbors)
, m_DataArrayPtr(dataArrayPtr)
Expand Down Expand Up @@ -54,14 +53,31 @@ class ErodeDilateBadDataTransferDataImpl
}

private:
ErodeDilateBadData* m_FilterAlg = nullptr;
usize m_TotalPoints = 0;
ChoicesParameter::ValueType m_Operation = 0;
std::vector<int64> m_Neighbors;
const std::vector<int64>& m_Neighbors;
const std::shared_ptr<IDataArray> m_DataArrayPtr;
const Int32AbstractDataStore& m_FeatureIds;
MessageHelper& m_MessageHelper;
};

/**
* @brief Masks out face neighbors whose axis has been disabled via the X/Y/Z Direction parameters.
* Indices follow the VoxelNeighbors<Image3D> ordering: [-Z,-Y,-X,+X,+Y,+Z].
* @param isValidFaceNeighbor Per-voxel face-neighbor validity, already computed from geometry boundary.
* @param xDir Whether the X direction is enabled.
* @param yDir Whether the Y direction is enabled.
* @param zDir Whether the Z direction is enabled.
*/
void adjustValidNeighbors(std::array<bool, VoxelNeighbors<Image3D>::k_FaceNeighborCount>& isValidFaceNeighbor, bool xDir, bool yDir, bool zDir)
{
isValidFaceNeighbor[VoxelNeighbors<Image3D>::k_NegativeZNeighbor] = isValidFaceNeighbor[VoxelNeighbors<Image3D>::k_NegativeZNeighbor] && zDir;
isValidFaceNeighbor[VoxelNeighbors<Image3D>::k_NegativeYNeighbor] = isValidFaceNeighbor[VoxelNeighbors<Image3D>::k_NegativeYNeighbor] && yDir;
isValidFaceNeighbor[VoxelNeighbors<Image3D>::k_NegativeXNeighbor] = isValidFaceNeighbor[VoxelNeighbors<Image3D>::k_NegativeXNeighbor] && xDir;
isValidFaceNeighbor[VoxelNeighbors<Image3D>::k_PositiveXNeighbor] = isValidFaceNeighbor[VoxelNeighbors<Image3D>::k_PositiveXNeighbor] && xDir;
isValidFaceNeighbor[VoxelNeighbors<Image3D>::k_PositiveYNeighbor] = isValidFaceNeighbor[VoxelNeighbors<Image3D>::k_PositiveYNeighbor] && yDir;
isValidFaceNeighbor[VoxelNeighbors<Image3D>::k_PositiveZNeighbor] = isValidFaceNeighbor[VoxelNeighbors<Image3D>::k_PositiveZNeighbor] && zDir;
}
} // namespace

// -----------------------------------------------------------------------------
Expand Down Expand Up @@ -110,6 +126,11 @@ Result<> ErodeDilateBadData::operator()()
}
}

MessageHelper messageHelper(m_MessageHandler);

// Build up a list of the DataArrays that we are going to operate on.
const std::vector<std::shared_ptr<IDataArray>> voxelArrays = nx::core::GenerateDataArrayList(m_DataStructure, m_InputValues->FeatureIdsArrayPath, m_InputValues->IgnoredDataArrayPaths);

constexpr FaceNeighborType k_NumFaceNeighbors = VoxelNeighbors<Image3D>::k_FaceNeighborCount;
const std::array<int64, k_NumFaceNeighbors> neighborVoxelIndexOffsets = initializeFaceNeighborOffsets(dims);
constexpr std::array<FaceNeighborType, k_NumFaceNeighbors> faceNeighborInternalIdx = initializeFaceNeighborInternalIdx();
Expand All @@ -120,6 +141,12 @@ Result<> ErodeDilateBadData::operator()()
{
for(int64 zIdx = 0; zIdx < dims[2]; zIdx++)
{
// Check if the algorithm should cancel
if(m_ShouldCancel)
{
return {};
}

const int64 zStride = dims[0] * dims[1] * zIdx;
for(int64 yIdx = 0; yIdx < dims[1]; yIdx++)
{
Expand All @@ -132,7 +159,8 @@ Result<> ErodeDilateBadData::operator()()
{
int32 most = 0;
// Loop over the 6 face neighbors of the voxel
const std::array<bool, k_NumFaceNeighbors> isValidFaceNeighbor = computeValidFaceNeighbors(xIdx, yIdx, zIdx, dims);
std::array<bool, k_NumFaceNeighbors> isValidFaceNeighbor = computeValidFaceNeighbors(xIdx, yIdx, zIdx, dims);
adjustValidNeighbors(isValidFaceNeighbor, m_InputValues->XDirOn, m_InputValues->YDirOn, m_InputValues->ZDirOn);
for(const auto& faceIndex : faceNeighborInternalIdx)
{
if(!isValidFaceNeighbor[faceIndex])
Expand Down Expand Up @@ -177,11 +205,6 @@ Result<> ErodeDilateBadData::operator()()
}
}

// Build up a list of the DataArrays that we are going to operate on.
const std::vector<std::shared_ptr<IDataArray>> voxelArrays = nx::core::GenerateDataArrayList(m_DataStructure, m_InputValues->FeatureIdsArrayPath, m_InputValues->IgnoredDataArrayPaths);

MessageHelper messageHelper(m_MessageHandler);

ParallelTaskAlgorithm taskRunner;
taskRunner.setParallelizationEnabled(true);
for(const auto& voxelArray : voxelArrays)
Expand All @@ -193,14 +216,15 @@ Result<> ErodeDilateBadData::operator()()
continue;
}

taskRunner.execute(ErodeDilateBadDataTransferDataImpl(this, totalPoints, m_InputValues->Operation, featureIds, neighbors, voxelArray, messageHelper));
taskRunner.execute(ErodeDilateBadDataTransferDataImpl(totalPoints, m_InputValues->Operation, featureIds, neighbors, voxelArray, messageHelper));
}
taskRunner.wait(); // This will spill over if the number of DataArrays to process does not divide evenly by the number of threads.

// Now update the feature Ids
auto featureIDataArray = m_DataStructure.getSharedDataAs<IDataArray>(m_InputValues->FeatureIdsArrayPath);
taskRunner.setParallelizationEnabled(false); // Do this to make the next call synchronous
taskRunner.execute(ErodeDilateBadDataTransferDataImpl(this, totalPoints, m_InputValues->Operation, featureIds, neighbors, featureIDataArray, messageHelper));
taskRunner.execute(ErodeDilateBadDataTransferDataImpl(totalPoints, m_InputValues->Operation, featureIds, neighbors, featureIDataArray, messageHelper));
taskRunner.wait(); // Redundant while parallelization is disabled, but keeps the "transfer is complete" invariant local.
}

return {};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "SimplnxCore/Filters/Algorithms/ErodeDilateBadData.hpp"

#include "simplnx/DataStructure/DataPath.hpp"
#include "simplnx/DataStructure/Geometry/ImageGeom.hpp"
#include "simplnx/Parameters/ArraySelectionParameter.hpp"
#include "simplnx/Parameters/AttributeMatrixSelectionParameter.hpp"
#include "simplnx/Parameters/BoolParameter.hpp"
Expand All @@ -18,6 +19,12 @@

using namespace nx::core;

namespace
{
constexpr int32 k_NoDirectionsError = -14601;
constexpr int32 k_NoGeometryDimensionsError = -14602;
} // namespace

namespace nx::core
{

Expand Down Expand Up @@ -97,13 +104,29 @@ IFilter::PreflightResult ErodeDilateBadDataFilter::preflightImpl(const DataStruc
auto pOperationValue = filterArgs.value<ChoicesParameter::ValueType>(k_Operation_Key);
auto pFeatureIdsArrayPathValue = filterArgs.value<DataPath>(k_CellFeatureIdsArrayPath_Key);
auto pIgnoredDataArrayPathsValue = filterArgs.value<MultiArraySelectionParameter::ValueType>(k_IgnoredDataArrayPaths_Key);
auto xDirOn = filterArgs.value<bool>(k_XDirOn_Key);
auto yDirOn = filterArgs.value<bool>(k_YDirOn_Key);
auto zDirOn = filterArgs.value<bool>(k_ZDirOn_Key);
auto imageGeometryPath = filterArgs.value<DataPath>(k_SelectedImageGeometryPath_Key);

PreflightResult preflightResult;

nx::core::Result<OutputActions> resultOutputActions;

std::vector<PreflightValue> preflightUpdatedValues;

if(!xDirOn && !yDirOn && !zDirOn)
{
return {MakeErrorResult<OutputActions>(k_NoDirectionsError, "ErodeDilateBadData requires at least one direction to operate over")};
}

const auto& imageGeom = dataStructure.getDataRefAs<ImageGeom>(imageGeometryPath);
auto dims = imageGeom.getDimensions();
if(dims[0] == 0 || dims[1] == 0 || dims[2] == 0)
{
return {MakeErrorResult<OutputActions>(k_NoGeometryDimensionsError, "ErodeDilateBadData requires that the ImageGeom have its dimensions set. No dimension may be 0.")};
}

std::string featureModificationWarning = "By modifying the cell level data, any feature data that was previously computed will most likely be invalid at this point. Filters that compute feature "
"level data should be rerun to ensure accurate final results from your pipeline.";
preflightUpdatedValues.emplace_back(PreflightValue{"Feature Data Modification Warning", featureModificationWarning});
Expand Down
Loading
Loading