Skip to content

Commit 4e6e1f6

Browse files
committed
ENH: Split BadDataNeighborOrientationCheck into in-core and OOC algorithms
Split BadDataNeighborOrientationCheck into two dispatched algorithms using DispatchAlgorithm for optimal performance in both configurations: - Worklist (in-core): Uses std::deque worklist for Phase 2 to process only eligible voxels with fast random access. ~5x speedup vs original. - Scanline (OOC): Uses chunk-sequential multi-pass scans for Phase 2 to avoid random access chunk thrashing. Includes chunk-skip optimization that checks in-memory neighborCount before loading chunks, skipping those with no eligible voxels. ~1.8x speedup vs original. Both algorithms share Phase 1 (chunk-sequential neighbor counting) and use only a single neighborCount vector (4 bytes/voxel) with no additional large allocations. Updated tests with GENERATE + ForceOocAlgorithmGuard to exercise both algorithm paths in in-core builds. Added 200x200x200 benchmark test. Signed-off-by: Joey Kleingers <[email protected]>
1 parent 8e79b9c commit 4e6e1f6

File tree

7 files changed

+745
-172
lines changed

7 files changed

+745
-172
lines changed

src/Plugins/OrientationAnalysis/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,8 @@ set(filter_algorithms
168168
AlignSectionsMisorientation
169169
AlignSectionsMutualInformation
170170
BadDataNeighborOrientationCheck
171+
BadDataNeighborOrientationCheckScanline
172+
BadDataNeighborOrientationCheckWorklist
171173
CAxisSegmentFeatures
172174
ComputeAvgCAxes
173175
ComputeAvgOrientations
Lines changed: 9 additions & 172 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
#include "BadDataNeighborOrientationCheck.hpp"
22

3-
#include "simplnx/Common/Numbers.hpp"
4-
#include "simplnx/DataStructure/DataArray.hpp"
5-
#include "simplnx/DataStructure/Geometry/ImageGeom.hpp"
6-
#include "simplnx/Utilities/MaskCompareUtilities.hpp"
7-
#include "simplnx/Utilities/MessageHelper.hpp"
8-
#include "simplnx/Utilities/NeighborUtilities.hpp"
3+
#include "BadDataNeighborOrientationCheckScanline.hpp"
4+
#include "BadDataNeighborOrientationCheckWorklist.hpp"
95

10-
#include <EbsdLib/LaueOps/LaueOps.h>
6+
#include "simplnx/DataStructure/DataArray.hpp"
7+
#include "simplnx/Utilities/AlgorithmDispatch.hpp"
118

129
using namespace nx::core;
1310

@@ -33,170 +30,10 @@ const std::atomic_bool& BadDataNeighborOrientationCheck::getCancel()
3330
// -----------------------------------------------------------------------------
3431
Result<> BadDataNeighborOrientationCheck::operator()()
3532
{
36-
const float misorientationTolerance = m_InputValues->MisorientationTolerance * numbers::pi_v<float> / 180.0f;
37-
38-
const auto* imageGeomPtr = m_DataStructure.getDataAs<ImageGeom>(m_InputValues->ImageGeomPath);
39-
SizeVec3 udims = imageGeomPtr->getDimensions();
40-
const auto& cellPhases = m_DataStructure.getDataRefAs<Int32Array>(m_InputValues->CellPhasesArrayPath);
41-
const auto& quats = m_DataStructure.getDataRefAs<Float32Array>(m_InputValues->QuatsArrayPath);
42-
const auto& crystalStructures = m_DataStructure.getDataRefAs<UInt32Array>(m_InputValues->CrystalStructuresArrayPath);
43-
const usize totalPoints = quats.getNumberOfTuples();
44-
45-
std::unique_ptr<MaskCompareUtilities::MaskCompare> maskCompare;
46-
try
47-
{
48-
maskCompare = MaskCompareUtilities::InstantiateMaskCompare(m_DataStructure, m_InputValues->MaskArrayPath);
49-
} catch(const std::out_of_range& exception)
50-
{
51-
// This really should NOT be happening as the path was verified during preflight, BUT we may be calling this from
52-
// somewhere else that is NOT going through the normal nx::core::IFilter API of Preflight and Execute
53-
return MakeErrorResult(-54900, fmt::format("Mask Array DataPath does not exist or is not of the correct type (Bool | UInt8) {}", m_InputValues->MaskArrayPath.toString()));
54-
}
55-
56-
std::array<int64, 3> dims = {
57-
static_cast<int64>(udims[0]),
58-
static_cast<int64>(udims[1]),
59-
static_cast<int64>(udims[2]),
60-
};
61-
62-
std::array<int64, 6> neighborVoxelIndexOffsets = initializeFaceNeighborOffsets(dims);
63-
std::array<FaceNeighborType, 6> faceNeighborInternalIdx = initializeFaceNeighborInternalIdx();
64-
65-
std::vector<ebsdlib::LaueOps::Pointer> orientationOps = ebsdlib::LaueOps::GetAllOrientationOps();
66-
67-
std::vector<int32> neighborCount(totalPoints, 0);
68-
69-
MessageHelper messageHelper(m_MessageHandler);
70-
ThrottledMessenger throttledMessenger = messageHelper.createThrottledMessenger();
71-
// Loop over every point finding the number of neighbors that fall within the
72-
// user defined angle tolerance.
73-
for(int64 voxelIndex = 0; voxelIndex < totalPoints; voxelIndex++)
74-
{
75-
throttledMessenger.sendThrottledMessage([&] { return fmt::format("Processing Data {:.2f}% completed", CalculatePercentComplete(voxelIndex, totalPoints)); });
76-
// If the mask was set to false, then we check this voxel
77-
if(!maskCompare->isTrue(voxelIndex))
78-
{
79-
// We precalculate the positive voxel quaternion and laue class here to prevent reading and recalculating it for each face below
80-
ebsdlib::QuatD quat1(quats[voxelIndex * 4], quats[voxelIndex * 4 + 1], quats[voxelIndex * 4 + 2], quats[voxelIndex * 4 + 3]);
81-
quat1.positiveOrientation();
82-
const uint32 laueClass1 = crystalStructures[cellPhases[voxelIndex]];
83-
84-
int64 xIdx = voxelIndex % dims[0];
85-
int64 yIdx = (voxelIndex / dims[0]) % dims[1];
86-
int64 zIdx = voxelIndex / (dims[0] * dims[1]);
87-
88-
// Loop over the 6 face neighbors of the voxel
89-
std::array<bool, 6> isValidFaceNeighbor = computeValidFaceNeighbors(xIdx, yIdx, zIdx, dims);
90-
for(const auto& faceIndex : faceNeighborInternalIdx)
91-
{
92-
if(!isValidFaceNeighbor[faceIndex])
93-
{
94-
continue;
95-
}
96-
const int64 neighborPoint = voxelIndex + neighborVoxelIndexOffsets[faceIndex];
97-
98-
// Now compare the mask of the neighbor. If the mask is TRUE, i.e., that voxel
99-
// did not fail the threshold filter that most likely produced the mask array,
100-
// then we can look at that voxel.
101-
if(maskCompare->isTrue(neighborPoint))
102-
{
103-
// Both Cell Phases MUST be the same and be a valid Phase
104-
if(cellPhases[voxelIndex] == cellPhases[neighborPoint] && cellPhases[voxelIndex] > 0)
105-
{
106-
ebsdlib::QuatD quat2(quats[neighborPoint * 4], quats[neighborPoint * 4 + 1], quats[neighborPoint * 4 + 2], quats[neighborPoint * 4 + 3]);
107-
quat2.positiveOrientation();
108-
// Compute the Axis_Angle misorientation between those 2 quaternions
109-
ebsdlib::AxisAngleDType axisAngle = orientationOps[laueClass1]->calculateMisorientation(quat1, quat2);
110-
// if the angle is less than our tolerance, then we increment the neighbor count
111-
// for this voxel
112-
if(axisAngle[3] < misorientationTolerance)
113-
{
114-
neighborCount[voxelIndex]++;
115-
}
116-
}
117-
}
118-
}
119-
}
120-
}
121-
122-
constexpr int32 startLevel = 6;
123-
int32 currentLevel = startLevel;
124-
int32 counter = 0;
125-
126-
// Now we loop over all the points again, but this time we do it as many times
127-
// as the user has requested to iteratively flip voxels
128-
while(currentLevel >= m_InputValues->NumberOfNeighbors)
129-
{
130-
counter = 1;
131-
int32 loopNumber = 0;
132-
while(counter > 0)
133-
{
134-
counter = 0; // Set this while control variable to zero
135-
for(usize voxelIndex = 0; voxelIndex < totalPoints; voxelIndex++)
136-
{
137-
throttledMessenger.sendThrottledMessage([&] {
138-
return fmt::format("Level '{}' of '{}' || Processing Data ('{}') {:.2f}% completed", (startLevel - currentLevel) + 1, startLevel - m_InputValues->NumberOfNeighbors, loopNumber,
139-
CalculatePercentComplete(voxelIndex, totalPoints));
140-
});
141-
142-
// We are comparing the number-of-neighbors of the current voxel, and if it
143-
// is > the current level and the mask is FALSE, then we drop into this
144-
// conditional. The first thing that happens in the conditional is that
145-
// the current voxel's mask value is set to TRUE.
146-
if(neighborCount[voxelIndex] >= currentLevel && !maskCompare->isTrue(voxelIndex))
147-
{
148-
maskCompare->setValue(voxelIndex, true); // the current voxel's mask value is set to TRUE.
149-
counter++; // Increment the `counter` to force the loop to iterate again
150-
151-
// We precalculate the positive voxel quaternion and laue class here to prevent reading and recalculating it for each face below
152-
ebsdlib::QuatD quat1(quats[voxelIndex * 4], quats[voxelIndex * 4 + 1], quats[voxelIndex * 4 + 2], quats[voxelIndex * 4 + 3]);
153-
quat1.positiveOrientation();
154-
const uint32 laueClass1 = crystalStructures[cellPhases[voxelIndex]];
155-
156-
// This whole section below is to now look at the neighbor voxels of the
157-
// current voxel that just got flipped to true. This is needed because
158-
// if any of those neighbor's mask was `false`, then its neighbor count
159-
// is now not correct and will be off-by-one. So we run _almost_ the same
160-
// loop code as above but checking the specific neighbors of the current
161-
// voxel. This part should be termed the "Update Neighbor's Neighbor Count"
162-
int64 xIdx = voxelIndex % dims[0];
163-
int64 yIdx = (voxelIndex / dims[0]) % dims[1];
164-
int64 zIdx = voxelIndex / (dims[0] * dims[1]);
165-
166-
// Loop over the 6 face neighbors of the voxel
167-
std::array<bool, 6> isValidFaceNeighbor = computeValidFaceNeighbors(xIdx, yIdx, zIdx, dims);
168-
for(const auto& faceIndex : faceNeighborInternalIdx)
169-
{
170-
if(!isValidFaceNeighbor[faceIndex])
171-
{
172-
continue;
173-
}
174-
175-
int64 neighborPoint = voxelIndex + neighborVoxelIndexOffsets[faceIndex];
176-
177-
// If the neighbor voxel's mask is false, then ....
178-
if(!maskCompare->isTrue(neighborPoint))
179-
{
180-
// Make sure both cells phase values are identical and valid
181-
if(cellPhases[voxelIndex] == cellPhases[neighborPoint] && cellPhases[voxelIndex] > 0)
182-
{
183-
ebsdlib::QuatD quat2(quats[neighborPoint * 4], quats[neighborPoint * 4 + 1], quats[neighborPoint * 4 + 2], quats[neighborPoint * 4 + 3]);
184-
quat2.positiveOrientation();
185-
// Quaternion Math is not commutative so do not reorder
186-
ebsdlib::AxisAngleDType axisAngle = orientationOps[laueClass1]->calculateMisorientation(quat1, quat2);
187-
if(axisAngle[3] < misorientationTolerance)
188-
{
189-
neighborCount[neighborPoint]++;
190-
}
191-
}
192-
}
193-
}
194-
}
195-
}
196-
++loopNumber;
197-
}
198-
currentLevel = currentLevel - 1;
199-
}
33+
auto* quatsArray = m_DataStructure.getDataAs<IDataArray>(m_InputValues->QuatsArrayPath);
34+
auto* maskArray = m_DataStructure.getDataAs<IDataArray>(m_InputValues->MaskArrayPath);
35+
auto* phasesArray = m_DataStructure.getDataAs<IDataArray>(m_InputValues->CellPhasesArrayPath);
20036

201-
return {};
37+
return DispatchAlgorithm<BadDataNeighborOrientationCheckWorklist, BadDataNeighborOrientationCheckScanline>({quatsArray, maskArray, phasesArray}, m_DataStructure, m_MessageHandler, m_ShouldCancel,
38+
m_InputValues);
20239
}

0 commit comments

Comments
 (0)