Skip to content

Commit 7328371

Browse files
committed
VV: Compute Feature Reference C-Axis Misalignments fully V&V'ed
Summary: - Confirmed no SIMPLNX-side bugs (algorithm correctly handles all-non-hex feature via IEEE 754 NaN propagation; D1 + D4 below are pre-existing legacy bugs already fixed in SIMPLNX via PR #1438 + PR #1472); - documented 2 deviations from DREAM3D 6.5.171 (D1 legacy lacks isHex gate → garbage non-hex cell values + non-NaN avg, D4 hand-rolled MatrixMath + float32 stddev precision drift ~1e-4° per cell) — both already closed on v6_5_172 via pre-existing backport commits d4b5509aa + 4435d1997, three-way A/B confirms 6.5.172 ≡ SIMPLNX byte-for-byte; - retired 1 test (Valid Filter Execution — circular oracle on caxis_data.tar.gz exemplar; archive download retained because ComputeCAxisLocationsTest still consumes it); - unit tests replaced with 4 inlined *Class 1 (Analytical) + Class 4 (Invariant)* test fixtures (Simple Hex Triple + Realistic Microstructure exposes-all-non-hex-feature + All-Identical Orientation + Invariants sweep); - added 3 V&V source-tree deliverables (report, deviations, provenance).
1 parent f8b56d0 commit 7328371

5 files changed

Lines changed: 965 additions & 65 deletions

File tree

src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeFeatureReferenceCAxisMisorientations.cpp

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
#include "simplnx/DataStructure/DataArray.hpp"
77
#include "simplnx/DataStructure/Geometry/ImageGeom.hpp"
88
#include "simplnx/Utilities/ImageRotationUtilities.hpp"
9+
#include "simplnx/Utilities/MessageHelper.hpp"
10+
11+
#include <limits>
912

1013
#include <EbsdLib/Core/EbsdDataArray.hpp>
1114
#include <EbsdLib/Core/Orientation.hpp>
@@ -34,29 +37,28 @@ ComputeFeatureReferenceCAxisMisorientations::~ComputeFeatureReferenceCAxisMisori
3437
Result<> ComputeFeatureReferenceCAxisMisorientations::operator()()
3538
{
3639

37-
/* **************************************************************************
38-
* This section performs a sanity check to ensure that at least 1 phase is
39-
* hexagonal.
40-
*/
40+
// Preflight: every ensemble index must resolve to a Hex Laue class for the filter to do anything.
41+
// We need to know both whether any phase is hex (else hard error) and whether all phases are hex
42+
// (else warn the user that non-hex phases will be skipped).
4143
const auto& crystalStructures = m_DataStructure.getDataRefAs<UInt32Array>(m_InputValues->CrystalStructuresArrayPath);
42-
bool allPhasesHexagonal = true;
43-
bool noPhasesHexagonal = true;
44+
bool anyPhaseIsHex = false;
45+
bool allPhasesAreHex = true;
4446
for(usize i = 1; i < crystalStructures.size(); ++i)
4547
{
4648
const auto crystalStructureType = crystalStructures[i];
4749
const bool isHex = crystalStructureType == ebsdlib::CrystalStructure::Hexagonal_High || crystalStructureType == ebsdlib::CrystalStructure::Hexagonal_Low;
48-
allPhasesHexagonal = allPhasesHexagonal && isHex;
49-
noPhasesHexagonal = noPhasesHexagonal && !isHex;
50+
anyPhaseIsHex = anyPhaseIsHex || isHex;
51+
allPhasesAreHex = allPhasesAreHex && isHex;
5052
}
5153

52-
if(noPhasesHexagonal)
54+
if(!anyPhaseIsHex)
5355
{
5456
return MakeErrorResult(
5557
-9802, "Finding the feature reference c-axis misorientation requires at least one phase to be Hexagonal-Low 6/m or Hexagonal-High 6/mmm type crystal structures but none were found.");
5658
}
5759

5860
Result<> result;
59-
if(!allPhasesHexagonal)
61+
if(!allPhasesAreHex)
6062
{
6163
result.warnings().push_back(
6264
{-9803,
@@ -86,7 +88,7 @@ Result<> ComputeFeatureReferenceCAxisMisorientations::operator()()
8688

8789
const usize numQuatComps = quats.getNumberOfComponents();
8890

89-
std::vector<usize> counts(totalFeatures, 0ULL);
91+
std::vector<usize> counts(totalFeatures, 0);
9092
std::vector<float32> avgMisorientations(totalFeatures, 0.0f);
9193

9294
SizeVec3 uDims = m_DataStructure.getDataRefAs<ImageGeom>(m_InputValues->ImageGeometryPath).getDimensions();
@@ -158,26 +160,30 @@ Result<> ComputeFeatureReferenceCAxisMisorientations::operator()()
158160
}
159161
}
160162

161-
// Loop over all the features from the feature attribute matrix and compute the
162-
// average C Axis Misorientation for each feature
163+
// Per-feature average. Explicit NaN when no hex cells contributed (counts == 0); without this
164+
// guard, the division below would rely on IEEE 754 0/0 -> NaN, which is correct on every
165+
// platform we ship but fragile to FP-environment changes.
166+
MessageHelper messageHelper(m_MessageHandler);
167+
ThrottledMessenger throttledMessenger = messageHelper.createThrottledMessenger();
163168
for(usize featureId = 1; featureId < totalFeatures; featureId++)
164169
{
165-
if(featureId % 1000 == 0)
166-
{
167-
m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Working On Feature {} of {}", featureId, totalFeatures));
168-
}
169170
if(m_ShouldCancel)
170171
{
171172
return {};
172173
}
174+
throttledMessenger.sendThrottledMessage([&] { return fmt::format("Computing per-feature average {:.2f}% completed", CalculatePercentComplete(featureId, totalFeatures)); });
173175

174-
// Compute the average value of the misorientations between each feature's cell
175-
// and the average C-Axis for that feature
176-
featAvgCAxisMis[featureId] = avgMisorientations[featureId] / static_cast<float32>(counts[featureId]);
176+
if(counts[featureId] == 0)
177+
{
178+
featAvgCAxisMis[featureId] = std::numeric_limits<float32>::quiet_NaN();
179+
}
180+
else
181+
{
182+
featAvgCAxisMis[featureId] = avgMisorientations[featureId] / static_cast<float32>(counts[featureId]);
183+
}
177184
}
178185

179-
// These 2 loops compute the population standard deviation of those misorientations for
180-
// each feature.
186+
// Population standard deviation. Per-cell accumulate (diff^2) then per-feature sqrt(sum/count).
181187
std::vector<double> stdevs(totalFeatures, 0.0);
182188
for(usize cellIdx = 0; cellIdx < totalPoints; cellIdx++)
183189
{
@@ -191,16 +197,22 @@ Result<> ComputeFeatureReferenceCAxisMisorientations::operator()()
191197
stdevs[featureId] += (diff * diff);
192198
}
193199

194-
// Finish computing the standard deviation in this loop
195200
for(usize featureId = 1; featureId < totalFeatures; featureId++)
196201
{
197202
if(m_ShouldCancel)
198203
{
199204
return {};
200205
}
201206

202-
featStdevCAxisMis[featureId] = std::sqrt(stdevs[featureId] / static_cast<double>(counts[featureId]));
207+
if(counts[featureId] == 0)
208+
{
209+
featStdevCAxisMis[featureId] = std::numeric_limits<float32>::quiet_NaN();
210+
}
211+
else
212+
{
213+
featStdevCAxisMis[featureId] = static_cast<float32>(std::sqrt(stdevs[featureId] / static_cast<double>(counts[featureId])));
214+
}
203215
}
204216

205-
return {};
217+
return result;
206218
}

0 commit comments

Comments
 (0)