BlueQuartzSoftware / BlueQuartzSoftware/simplnx
BUG: Remove/Extract Flagged Features hangs when Fill-in Removed Features is enabled
- Dominant language
- C++
- Stars
- 17
- Forks
- 13
- Avg merge
- 3d 4h
- Merged PRs (30d)
- 10
Description
## Summary
`Remove/Extract Flagged Features` hangs indefinitely when **Fill-in Removed Features** is enabled and
any vacated cell fails to acquire a fill source. There is no error and no progress — the dilation
loop spins forever and the pipeline must be killed.
## Root cause
`IdentifyNeighbors()` in `src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RemoveFlaggedFeatures.cpp`
tallies the most common feature among a bad cell's six face neighbors, but only records a fill source
inside the `found` branch (line 97):
```cpp
bool found = false;
for(usize featIndex = 0; featIndex < discoveredFeatures.size(); featIndex++)
{
if(discoveredFeatures[featIndex] == feature)
{
found = true;
numHits[featIndex]++;
current = numHits[featIndex];
if(current > most)
{
most = current;
storageArray[voxelIndex] = static_cast(neighborPoint); // line 97
}
break;
}
}
if(!found)
{
discoveredFeatures.push_back(feature); // line 104: no numHits increment, no source recorded
}
```
On the **first** sighting of a feature the entry is pushed to `discoveredFeatures` but `numHits` is
never incremented, so it stays `0`. A source is therefore only ever recorded on the **second or
later** sighting of the same feature.
Consequently a bad cell whose valid face neighbors all belong to **distinct** features, or which has
only a single valid neighbor, never receives a `storageArray` entry. `FindVoxelArrays()` copies
nothing into it, so it remains at `-1`. The next pass sees `featureName <= 0`, sets
`shouldLoop = true` again, and the `do { ... } while(shouldLoop)` loop in `RemoveFlaggedFeatures::operator()`
never terminates.
## Reproduction
A 5x2x1 `ImageGeom`, `FeatureIds` = `1 1 1 2 3 / 1 4 4 4 3`, feature sizes 4/1/2/3. Remove features 2
and 3 with **Fill-in Removed Features** enabled. `FeatureIds` becomes `1 1 1 -1 -1 / 1 4 4 4 -1` and
none of the three vacated cells can be filled:
| Cell | Valid neighbors | Source recorded? |
|------|-----------------|------------------|
| 3 | features {1, 4} — both first sightings | no |
| 4 | both neighbors are `-1` | no |
| 9 | feature {4} — single sighting | no |
The filter hangs.
## Why this was never caught
The fill path has **no execution test coverage**. `RemoveFlaggedFeaturesTest.cpp` passes
`FillRemovedFeatures = false` at both of its execute sites (lines 144 and 218); line 279 is only a
SIMPL-conversion argument assertion and never runs the algorithm.
On large 3D volumes a bad cell almost always has two or more face neighbors belonging to the same
feature, which satisfies the second-sighting condition and lets the loop terminate. The defect
surfaces on thin geometries (single-slice or single-voxel-deep volumes) and on small or isolated bad
regions.
## Proposed fix
Count the first sighting as a hit, which also makes `numHits` a true tally rather than a count of
repeat sightings:
```cpp
if(!found)
{
discoveredFeatures.push_back(feature);
numHits[discoveredFeatures.size() - 1] = 1;
if(1 > most)
{
most = 1;
storageArray[voxelIndex] = static_cast(neighborPoint);
}
}
```
Any cell with at least one valid neighbor then acquires a source. Cells with no valid neighbor on the
first pass are filled on a later pass once their neighbors are resolved, so the loop terminates as
long as at least one feature survives — which `FlagFeatures()` already guarantees by erroring when
every feature is flagged.
This changes behavior: previously such cells were left unfilled and the filter never returned; now
they are filled from the first-seen neighboring feature, ties going to the first encountered.
## Requested work
Beyond the fix, this filter should have the **entire V&V suite performed**:
* Oracle classification and independent expected output
* SIMPLNX vs. oracle reconciliation
* Comparison against legacy DREAM3D 6.5.171, written up as structured deviation entries
* Full test-coverage inventory, with every enumerated code path mapped to a named test
The fill-by-dilation path in particular needs real assertions on **which** surviving feature each
vacated cell is assigned to, not merely that no cell is left at zero. An infinite loop reachable from
a checkbox in the GUI, in a code path with no execution coverage, is exactly the class of defect the
V&V process exists to surface.
For context, the neighboring filters in this family — `FillBadData` and
`ReplaceElementAttributesWithNeighborValues` — are already V&V'ed;
`Remove/Extract Flagged Features` is not.
Contributor guide
Assessment
This issue has not been assessed yet.