From 67aeda7ce584ac285380f13767904533fd54fb9e Mon Sep 17 00:00:00 2001 From: Michael Jackson Date: Tue, 30 Jun 2026 14:51:27 -0400 Subject: [PATCH 1/5] =?UTF-8?q?BUG:=20ComputeFaceIPFColoring=20=E2=80=94?= =?UTF-8?q?=20use=20Phase=202's=20Laue=20ops=20on=20the=20second=20face=20?= =?UTF-8?q?side?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes the issue #1635 wrong-phase mis-coloring. The Phase-2 branch of CalculateFaceIPFColorsImpl indexed m_CrystalStructures[phase1] in both the crystal-symmetry validity guard and the IPF operator lookup, while correctly reading feature2's Euler angles and the flipped face normal. It therefore applied Phase 1's symmetry operators to Phase 2's orientation. * Change the two phase1 indices to phase2 (Algorithms/ComputeFaceIPFColoring.cpp) and remove the KNOWN BUG block. Matches the legacy 6.5.172 backport 1c96b3b8e. * Two manifestations are corrected: wrong Laue operator on mixed-Laue-group faces, and black second color on faces whose first side is the exterior (feature1 <= 0) — the latter affects even single-phase data. * Replace the circular "Valid filter execution" test (it compared against the filter's own pre-fix output baked into 6_6_Small_IN100_GBCD) with a Class 1 analytical test: a hand-built 4-face cubic/hex mesh whose expected colors are closed-form IPF standard-triangle corner primaries. Verified to fail when the bug is reintroduced. V&V (issue #1635): * Three-way legacy binary A/B (6.5.171 / 6.5.172 / nxrunner) on a legacy-native cubic/hex fixture: 6.5.171 reproduces the buggy oracle; 6.5.172 and SIMPLNX both apply the hex operator (D1 fixed). Real 756,474-face A/B: SIMPLNX-fixed vs the baked buggy baseline differs on exactly 120,000 feature1<=0 boundary faces, 0 elsewhere. * Deviation D2 (library): EbsdLib colors the hex basal direction green; legacy DREAM3D's older EbsdLib colors it blue (cubic unaffected). Resolved: EbsdLib is canonical and 3.0.0 and 3.1.0 agree on green (the Class 1 oracle passes unchanged on a from-source EbsdLib 3.1.0 build); legacy blue is the deviation, trust SIMPLNX. The convention-independent oracle assertion is red-channel==0. * Add V&V deliverables: report, deviations (D1 + D2), provenance note, legacy comparison summary, and reproducible fixture/pipelines. * Clarify per-phase symmetry behavior and the 6.5.171 migration note in docs. Note: the NX-written 6_6_Small_IN100_GBCD surface mesh is not readable by legacy DataContainerReader (DataArray connectivity); the legacy fixture was authored with the legacy_dream3d writer. Worth a separate NX-export issue. Signed-off-by: Michael Jackson --- .../docs/ComputeFaceIPFColoringFilter.md | 4 + .../Algorithms/ComputeFaceIPFColoring.cpp | 10 +- .../test/ComputeFaceIPFColoringTest.cpp | 144 +++++++++++---- .../vv/ComputeFaceIPFColoringFilter.md | 102 +++++++++++ .../ComputeFaceIPFColoringFilter.md | 59 ++++++ ...teFaceIPFColoringFilter-circular-oracle.md | 28 +++ vv_work/face_ipf/build_fixture.py | 51 ++++++ vv_work/face_ipf/fx_6_5_171.json | 170 ++++++++++++++++++ vv_work/face_ipf/fx_6_5_172.json | 170 ++++++++++++++++++ vv_work/face_ipf/gen_nx_pipeline.py | 48 +++++ vv_work/face_ipf/gen_pipeline.py | 128 +++++++++++++ vv_work/face_ipf/legacy_FaceIPF_6_5_171.json | 170 ++++++++++++++++++ vv_work/face_ipf/legacy_FaceIPF_6_5_172.json | 170 ++++++++++++++++++ vv_work/face_ipf/legacy_comparison_summary.md | 82 +++++++++ vv_work/face_ipf/nx_FaceIPF.d3dpipeline | 83 +++++++++ 15 files changed, 1378 insertions(+), 41 deletions(-) create mode 100644 src/Plugins/OrientationAnalysis/vv/ComputeFaceIPFColoringFilter.md create mode 100644 src/Plugins/OrientationAnalysis/vv/deviations/ComputeFaceIPFColoringFilter.md create mode 100644 src/Plugins/OrientationAnalysis/vv/provenance/ComputeFaceIPFColoringFilter-circular-oracle.md create mode 100644 vv_work/face_ipf/build_fixture.py create mode 100644 vv_work/face_ipf/fx_6_5_171.json create mode 100644 vv_work/face_ipf/fx_6_5_172.json create mode 100644 vv_work/face_ipf/gen_nx_pipeline.py create mode 100644 vv_work/face_ipf/gen_pipeline.py create mode 100644 vv_work/face_ipf/legacy_FaceIPF_6_5_171.json create mode 100644 vv_work/face_ipf/legacy_FaceIPF_6_5_172.json create mode 100644 vv_work/face_ipf/legacy_comparison_summary.md create mode 100644 vv_work/face_ipf/nx_FaceIPF.d3dpipeline diff --git a/src/Plugins/OrientationAnalysis/docs/ComputeFaceIPFColoringFilter.md b/src/Plugins/OrientationAnalysis/docs/ComputeFaceIPFColoringFilter.md index 409d71c9db..c2821369ca 100644 --- a/src/Plugins/OrientationAnalysis/docs/ComputeFaceIPFColoringFilter.md +++ b/src/Plugins/OrientationAnalysis/docs/ComputeFaceIPFColoringFilter.md @@ -8,6 +8,10 @@ Processing (Crystallography) This **Filter** generates a pair of colors for each **Triangle** in a **Triangle Geometry** based on the inverse pole figure (IPF) color scheme for the present crystal structure. Each **Triangle** has 2 colors since any **Face** sits at a boundary between 2 **Features** for a well-connected set of **Features** that represent _grains_. The reference direction used for the IPF color generation is the _normal_ of the **Triangle**. +Each side of a **Face** is colored using **that side's own** feature: the first color uses the first feature's orientation, its phase's crystal symmetry (Laue group), and the face normal; the second color uses the second feature's orientation, **its own** phase's crystal symmetry, and the negated (inward) face normal. This matters for multi-phase microstructures where the two features adjacent to a **Face** belong to phases with different Laue groups (for example a cubic phase against a hexagonal phase) — each side is colored with the correct symmetry for its phase. A side whose feature is invalid (a boundary/exterior face, feature id ≤ 0) is colored black. + +> **Note on migration from DREAM3D 6.5.171:** earlier DREAM3D 6.5.171 colored the *second* side of every **Face** using the *first* feature's crystal symmetry, which produced incorrect colors on mixed-Laue-group faces and left the second color black on faces whose first side was the exterior. DREAM3D-NX (and DREAM3D 6.5.172) compute each side with its own phase's symmetry. See V&V deviation `ComputeFaceIPFColoringFilter-D1`. + ------------ ![Face IPF Coloring](Images/ComputeFaceIPFColoring.png) diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeFaceIPFColoring.cpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeFaceIPFColoring.cpp index 2885bcf9b8..851468065c 100644 --- a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeFaceIPFColoring.cpp +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeFaceIPFColoring.cpp @@ -106,12 +106,8 @@ class CalculateFaceIPFColorsImpl // Now compute for Phase 2 if(phase2 > 0) { - // KNOWN BUG — tracked at https://github.com/BlueQuartzSoftware/simplnx/issues/1635. - // Both the validity guard and the IPF operator lookup below use phase1 instead of phase2, - // assigning Phase 1's symmetry operator to Phase 2's Euler angles on mixed-phase faces. - // The 2-line fix (phase1→phase2) is held pending a V&V cycle that will regenerate the - // test exemplar (which currently encodes the bug — a circular oracle). - if(m_CrystalStructures[phase1] < ebsdlib::CrystalStructure::LaueGroupEnd) + // Make sure we are using a valid Euler Angles with valid crystal symmetry + if(m_CrystalStructures[phase2] < ebsdlib::CrystalStructure::LaueGroupEnd) { dEuler[0] = m_Eulers[3 * feature2 + 0]; dEuler[1] = m_Eulers[3 * feature2 + 1]; @@ -120,7 +116,7 @@ class CalculateFaceIPFColorsImpl refDir[1] = -m_Normals[3 * i + 1]; refDir[2] = -m_Normals[3 * i + 2]; - argb = ops[m_CrystalStructures[phase1]]->generateIPFColor(dEuler, refDir, false, m_ColorKey); + argb = ops[m_CrystalStructures[phase2]]->generateIPFColor(dEuler, refDir, false, m_ColorKey); m_SecondColors[3 * i + 0] = RgbColor::dRed(argb); m_SecondColors[3 * i + 1] = RgbColor::dGreen(argb); m_SecondColors[3 * i + 2] = RgbColor::dBlue(argb); diff --git a/src/Plugins/OrientationAnalysis/test/ComputeFaceIPFColoringTest.cpp b/src/Plugins/OrientationAnalysis/test/ComputeFaceIPFColoringTest.cpp index ef2ff2cdc5..5e67e7c4b5 100644 --- a/src/Plugins/OrientationAnalysis/test/ComputeFaceIPFColoringTest.cpp +++ b/src/Plugins/OrientationAnalysis/test/ComputeFaceIPFColoringTest.cpp @@ -2,6 +2,9 @@ #include "OrientationAnalysis/OrientationAnalysis_test_dirs.hpp" #include "simplnx/Core/Application.hpp" +#include "simplnx/DataStructure/DataArray.hpp" +#include "simplnx/DataStructure/DataGroup.hpp" +#include "simplnx/DataStructure/DataStructure.hpp" #include "simplnx/Parameters/ArrayCreationParameter.hpp" #include "simplnx/Parameters/ChoicesParameter.hpp" #include "simplnx/Pipeline/Pipeline.hpp" @@ -11,6 +14,7 @@ #include #include +#include #include #include @@ -20,7 +24,6 @@ namespace fs = std::filesystem; namespace { -constexpr StringLiteral k_FaceIPFColors("SurfaceMeshFaceIPFColors"); constexpr StringLiteral k_FirstNXFaceIPFColors("NXFaceIPFColors 0"); constexpr StringLiteral k_SecondNXFaceIPFColors("NXFaceIPFColors 1"); @@ -39,57 +42,130 @@ DataPath faceNormals = faceDataGroup.createChildPath(nx::core::Constants::k_Face DataPath faceAreas = faceDataGroup.createChildPath(nx::core::Constants::k_FaceAreas); } // namespace -TEST_CASE("OrientationAnalysis::ComputeFaceIPFColoringFilter: Valid filter execution", "[OrientationAnalysis][ComputeFaceIPFColoringFilter]") +// ============================================================================= +// Class 1 (Analytical) oracle for issue #1635. +// +// The filter colors each side of a surface-mesh face with the IPF color of the +// adjacent feature: the FIRST color uses feature1's orientation + the face +// normal; the SECOND color uses feature2's orientation + the *negated* face +// normal. Each side must use ITS OWN phase's Laue symmetry operator. +// +// Oracle: IPF colors are a closed-form function of (orientation, reference +// direction, Laue symmetry). At the standard-triangle corners the color is a +// pure primary, independent of any implementation detail (EbsdLib 3.0.0, +// TSL color key): +// - Cubic-high <100> -> (255, 0, 0) (red corner) +// - Cubic-high <111> -> ( 0, 0,255) (blue corner) +// - Hex-high c-axis -> (255, 0, 0) (red corner) +// - Hex-high basal -> ( 0,255, 0) (green corner; RED channel is exactly +// 0 because a basal direction sits at +// chi = chiMax, so r = 1 - chi/chiMax = 0) +// The crystal-structure enum values that index ops[] are Hexagonal_High = 0, +// Cubic_High = 1 (EbsdLib/Core/EbsdLibConstants.h). +// +// The fixture (identity orientations throughout) builds four faces: +// Face 0 labels (1,2) n=(-1,0,0) mixed cubic/hex -- the headline discriminator +// Face 1 labels (-1,2) n=(-1,0,0) feature1 invalid -- boundary discriminator +// Face 2 labels (1,-1) n=(-1,0,0) feature2 invalid -- control (second black) +// Face 3 labels (1,3) n=(1,1,1) same-phase cubic -- control (bug cannot manifest) +// +// Why each side's expected color is what it is (FIX = correct behavior): +// F0 first = cubic(+(-1,0,0)) = <100> red = (255, 0, 0) +// F0 second = hex (-(-1,0,0)=(1,0,0)) basal = ( 0,255, 0) [BUG -> cubic <100> = (255,0,0)] +// F1 first = black (feature1 = -1 -> phase1 = 0) = ( 0, 0, 0) +// F1 second = hex (1,0,0) basal = ( 0,255, 0) [BUG -> guard reads CrystalStructures[0] +// sentinel, fails, leaves black (0,0,0)] +// F2 first = cubic(-1,0,0) = <100> red = (255, 0, 0) +// F2 second = black (feature2 = -1 -> phase2 = 0) = ( 0, 0, 0) +// F3 first = cubic(1,1,1) = <111> blue = ( 0, 0,255) +// F3 second = cubic(-(1,1,1)) = <111> blue = ( 0, 0,255) [phase1==phase2, bug cannot manifest] +// +// Faces 0 and 1 are the discriminators: under the pre-fix bug both their SECOND +// colors are wrong (F0 -> red, F1 -> black); after the fix they are green. +TEST_CASE("OrientationAnalysis::ComputeFaceIPFColoringFilter: Class 1 Oracle - mixed-phase analytical", "[OrientationAnalysis][ComputeFaceIPFColoringFilter]") { UnitTest::LoadPlugins(); - const nx::core::UnitTest::TestFileSentinel testDataSentinel(nx::core::unit_test::k_TestFilesDir, "6_6_Small_IN100_GBCD.tar.gz", "6_6_Small_IN100_GBCD"); + // EbsdLib crystal-structure enum values used as ops[] indices. + constexpr uint32 k_HexHigh = 0; // Hexagonal_High (6/mmm) + constexpr uint32 k_CubicHigh = 1; // Cubic_High (m-3m) + constexpr uint32 k_Unknown = 999; // UnknownCrystalStructure sentinel + + DataStructure dataStructure; + auto* topGroup = DataGroup::Create(dataStructure, "Data"); + + const DataPath faceLabelsPath({"Data", "FaceLabels"}); + const DataPath faceNormalsPath({"Data", "FaceNormals"}); + const DataPath eulerAnglesPath({"Data", "FeatureEulerAngles"}); + const DataPath phasesPath({"Data", "FeaturePhases"}); + const DataPath crystalStructuresPath({"Data", "CrystalStructures"}); + + // 4 faces, 2-component (feature1, feature2) + auto* faceLabelsArray = UnitTest::CreateTestDataArray(dataStructure, "FaceLabels", {4}, {2}, topGroup->getId()); + const std::vector faceLabelValues = {1, 2, -1, 2, 1, -1, 1, 3}; + std::copy(faceLabelValues.begin(), faceLabelValues.end(), faceLabelsArray->begin()); + + // 4 faces, 3-component face normals (not normalized; EbsdLib normalizes internally) + auto* faceNormalsArray = UnitTest::CreateTestDataArray(dataStructure, "FaceNormals", {4}, {3}, topGroup->getId()); + const std::vector faceNormalValues = {-1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, 1.0, 1.0, 1.0}; + std::copy(faceNormalValues.begin(), faceNormalValues.end(), faceNormalsArray->begin()); + + // 4 features (index 0 unused), identity Euler angles throughout + auto* eulerAngles = UnitTest::CreateTestDataArray(dataStructure, "FeatureEulerAngles", {4}, {3}, topGroup->getId()); + std::fill(eulerAngles->begin(), eulerAngles->end(), 0.0F); + + // Feature phases: feature 1 -> phase 1 (cubic), feature 2 -> phase 2 (hex), feature 3 -> phase 1 (cubic) + auto* phases = UnitTest::CreateTestDataArray(dataStructure, "FeaturePhases", {4}, {1}, topGroup->getId()); + const std::vector phaseValues = {0, 1, 2, 1}; + std::copy(phaseValues.begin(), phaseValues.end(), phases->begin()); + + // Crystal structures per phase: phase 0 -> unknown, phase 1 -> cubic-high, phase 2 -> hex-high + auto* crystalStructures = UnitTest::CreateTestDataArray(dataStructure, "CrystalStructures", {3}, {1}, topGroup->getId()); + (*crystalStructures)[0] = k_Unknown; + (*crystalStructures)[1] = k_CubicHigh; + (*crystalStructures)[2] = k_HexHigh; - // Read the Small IN100 Data set - auto baseDataFilePath = fs::path(fmt::format("{}/6_6_Small_IN100_GBCD/6_6_Small_IN100_GBCD.dream3d", unit_test::k_TestFilesDir)); - DataStructure dataStructure = UnitTest::LoadDataStructure(baseDataFilePath); - - // Instantiate the filter, a DataStructure object and an Arguments Object ComputeFaceIPFColoringFilter filter; Arguments args; - - // Create default Parameters for the filter. - args.insertOrAssign(ComputeFaceIPFColoringFilter::k_SurfaceMeshFaceLabelsArrayPath_Key, std::make_any(faceLabels)); - args.insertOrAssign(ComputeFaceIPFColoringFilter::k_SurfaceMeshFaceNormalsArrayPath_Key, std::make_any(faceNormals)); - args.insertOrAssign(ComputeFaceIPFColoringFilter::k_FeatureEulerAnglesArrayPath_Key, std::make_any(avgEulerAnglesPath)); - args.insertOrAssign(ComputeFaceIPFColoringFilter::k_FeaturePhasesArrayPath_Key, std::make_any(featurePhasesPath)); - args.insertOrAssign(ComputeFaceIPFColoringFilter::k_CrystalStructuresArrayPath_Key, std::make_any(crystalStructurePath)); + args.insertOrAssign(ComputeFaceIPFColoringFilter::k_SurfaceMeshFaceLabelsArrayPath_Key, std::make_any(faceLabelsPath)); + args.insertOrAssign(ComputeFaceIPFColoringFilter::k_SurfaceMeshFaceNormalsArrayPath_Key, std::make_any(faceNormalsPath)); + args.insertOrAssign(ComputeFaceIPFColoringFilter::k_FeatureEulerAnglesArrayPath_Key, std::make_any(eulerAnglesPath)); + args.insertOrAssign(ComputeFaceIPFColoringFilter::k_FeaturePhasesArrayPath_Key, std::make_any(phasesPath)); + args.insertOrAssign(ComputeFaceIPFColoringFilter::k_CrystalStructuresArrayPath_Key, std::make_any(crystalStructuresPath)); args.insertOrAssign(ComputeFaceIPFColoringFilter::k_FirstFaceIPFColorsArrayName_Key, std::make_any(::k_FirstNXFaceIPFColors)); args.insertOrAssign(ComputeFaceIPFColoringFilter::k_SecondFaceIPFColorsArrayName_Key, std::make_any(::k_SecondNXFaceIPFColors)); + args.insertOrAssign(ComputeFaceIPFColoringFilter::k_ColorKey_Key, std::make_any(0)); // TSL - // Preflight the filter and check result auto preflightResult = filter.preflight(dataStructure, args); SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions); - - // Execute the filter and check the result auto executeResult = filter.execute(dataStructure, args); SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result); - // compare the resulting face IPF Colors array - DataPath exemplarPath = faceDataGroup.createChildPath(::k_FaceIPFColors); - const auto& exemplarArray = dataStructure.getDataRefAs(exemplarPath); - DataPath firstGeneratedPath = faceDataGroup.createChildPath(::k_FirstNXFaceIPFColors); - const auto& firstArray = dataStructure.getDataRefAs(firstGeneratedPath); - DataPath secondGeneratedPath = faceDataGroup.createChildPath(::k_SecondNXFaceIPFColors); - const auto& secondArray = dataStructure.getDataRefAs(secondGeneratedPath); + const auto& first = dataStructure.getDataRefAs(faceLabelsPath.replaceName(::k_FirstNXFaceIPFColors)); + const auto& second = dataStructure.getDataRefAs(faceLabelsPath.replaceName(::k_SecondNXFaceIPFColors)); - // Done this way because original output was segmented into separate arrays for vis - for(usize i = 0; i < exemplarArray.getNumberOfTuples(); i++) - { - REQUIRE(exemplarArray[(i * 6) + 0] == firstArray[(i * 3) + 0]); - REQUIRE(exemplarArray[(i * 6) + 1] == firstArray[(i * 3) + 1]); - REQUIRE(exemplarArray[(i * 6) + 2] == firstArray[(i * 3) + 2]); + // Expected colors (the FIX = correct behavior). Each row is one face. + const std::array, 4> expectedFirst = {{{255, 0, 0}, {0, 0, 0}, {255, 0, 0}, {0, 0, 255}}}; + const std::array, 4> expectedSecond = {{{0, 255, 0}, {0, 255, 0}, {0, 0, 0}, {0, 0, 255}}}; - REQUIRE(exemplarArray[(i * 6) + 3] == secondArray[(i * 3) + 0]); - REQUIRE(exemplarArray[(i * 6) + 4] == secondArray[(i * 3) + 1]); - REQUIRE(exemplarArray[(i * 6) + 5] == secondArray[(i * 3) + 2]); + for(usize face = 0; face < 4; face++) + { + INFO(fmt::format("face {}", face)); + REQUIRE(first[face * 3 + 0] == expectedFirst[face][0]); + REQUIRE(first[face * 3 + 1] == expectedFirst[face][1]); + REQUIRE(first[face * 3 + 2] == expectedFirst[face][2]); + + REQUIRE(second[face * 3 + 0] == expectedSecond[face][0]); + REQUIRE(second[face * 3 + 1] == expectedSecond[face][1]); + REQUIRE(second[face * 3 + 2] == expectedSecond[face][2]); } + // Class 4 companion invariant: the Phase-2 (hex) side of a basal-plane face has + // a RED channel of exactly 0. The pre-fix bug colored it with cubic <100> ops, + // which produces (255,0,0) -- a non-zero red channel. This single byte is the + // crispest signature of the wrong-Laue-operator bug. + REQUIRE(second[0 * 3 + 0] == 0); // Face 0 hex second color, red channel + UnitTest::CheckArraysInheritTupleDims(dataStructure); } diff --git a/src/Plugins/OrientationAnalysis/vv/ComputeFaceIPFColoringFilter.md b/src/Plugins/OrientationAnalysis/vv/ComputeFaceIPFColoringFilter.md new file mode 100644 index 0000000000..2548421f73 --- /dev/null +++ b/src/Plugins/OrientationAnalysis/vv/ComputeFaceIPFColoringFilter.md @@ -0,0 +1,102 @@ +# V&V Report: ComputeFaceIPFColoringFilter + +| | | +|--------|--------------| +| Plugin | OrientationAnalysis | +| SIMPLNX UUID | 30759600-7c02-4650-b5ca-e7036d6b568e | +| DREAM3D 6.5.171 equivalent | GenerateFaceIPFColoring (legacy SIMPL UUID `0a121e03-3922-5c29-962d-40d88653f4b6`) | +| Verified commit | ** | +| Status | READY FOR REVIEW | +| Sign-off | ** | + +## At a glance + +| Aspect | Current state | +|------------------------|------------------------------------------------------------------------------------------------------------------------------| +| Algorithm Relationship | **Port** of legacy `GenerateFaceIPFColoring` — per-face IPF color math is line-for-line identical. Deltas: output split from one 6-component array into two 3-component arrays, an added Color Key choice (TSL/PUCM/Nolze-Hielscher), and the EbsdLib 3.0.0 API. The 2023 wrong-phase bug (issue #1635) was ported verbatim and is **fixed here**. | +| Oracle (confirmed) | **Class 1 (Analytical)** + Class 4 companion — closed-form IPF corner colors (pure primaries) on a hand-built 4-face mixed cubic/hex mesh. Encoded as `Class 1 Oracle - mixed-phase analytical` (24 color-byte assertions), all pass; verified to **fail** when the bug is reintroduced. | +| Code paths enumerated | 11 of 13 exercised; the 2 uncovered are low-value guard branches (a feature's phase has an out-of-range crystal-structure index) noted below. | +| Tests today | 4 test cases — 1 Class-1 analytical (new-for-V&V), 1 negative (preflight tuple-mismatch, 2 sections), 1 Color-Key plumbing run on real Small IN100 GBCD data, 1 SIMPL backward-compat. | +| Exemplar archive | **None — analytical fixtures inlined.** The prior `Valid filter execution` test compared against a `SurfaceMeshFaceIPFColors` array baked into the shared `6_6_Small_IN100_GBCD.tar.gz`; that comparison was a circular oracle (filter's own pre-fix output) and is **retired**. | +| Legacy comparison | **Three-way binary run** (6.5.171 vs 6.5.172 vs SIMPLNX) on a hand-built legacy-native cubic/hex mesh + SIMPLNX-vs-baked on the 756,474-face real mesh. **2 deviations:** D1 (the #1635 bug — 6.5.171 wrong, fixed in both 6.5.172 and SIMPLNX) and D2 (EbsdLib hex basal hue: SIMPLNX green vs legacy blue). Cubic coloring identical across all three. | +| Bug flags | `…-D1` — wrong-phase Laue operator on the Phase-2 side (a 6.5.171 bug, fixed). `…-D2` — hex basal IPF hue differs between EbsdLib (NX, green) and legacy EbsdLib (blue); resolved as a library convention difference — EbsdLib is canonical (3.0.0 and 3.1.0 agree), legacy is the deviation, trust SIMPLNX. Affects all hex IPF coloring. | +| V&V phase | All phases complete: oracle chosen + applied before legacy comparison, fix applied, tests encode the oracle, deviation documented. Outstanding: second-engineer oracle review; optional before/after doc image. | + +## Summary + +`ComputeFaceIPFColoringFilter` assigns each side of every surface-mesh triangle an inverse-pole-figure (IPF) color from the adjacent feature's orientation, phase symmetry, and the face normal. It was verified with a Class 1 analytical oracle — a hand-built mixed cubic/hex mesh whose expected colors are the closed-form IPF standard-triangle corner primaries — which is independent of both DREAM3D versions. Verification surfaced and fixed the issue #1635 wrong-phase bug (the Phase-2 side used Phase-1's Laue operator); after the fix SIMPLNX matches the analytical oracle exactly and matches the corrected legacy 6.5.172, leaving one documented deviation from the still-buggy 6.5.171. + +## Algorithm Relationship + +**Port** + +*Evidence:* SIMPLNX inherits the legacy SIMPL UUID `0a121e03-3922-5c29-962d-40d88653f4b6` (see `OrientationAnalysisLegacyUUIDMapping.hpp`). The per-face coloring kernel (`CalculateFaceIPFColorsImpl::generate`) is a line-for-line translation of legacy `GenerateFaceIPFColoring.cpp`. + +*Port-time deltas (each assessed for output impact):* + +1. **Output layout** — legacy wrote a single 6-component `SurfaceMeshFaceIPFColors` array (`m_Colors[6*i+0..5]`); SIMPLNX writes two separate 3-component arrays (`FirstFaceIPFColors`, `SecondFaceIPFColors`). Pure repackaging — the six bytes per face are identical, only regrouped. No value change. +2. **Color Key parameter** — SIMPLNX adds a TSL/PUCM/Nolze-Hielscher choice routed into `generateIPFColor(..., m_ColorKey)`; legacy always used TSL. Default (TSL) reproduces legacy output exactly; the new option is additive. +3. **EbsdLib API** — `Ebsd::` → `ebsdlib::`, and `generateIPFColor` gained the `ColorKeyKind` argument. EbsdLib upgraded to 3.0.0. No change to the TSL color math at the standard-triangle corners used by the oracle. +4. **Parallelization** — both use TBB-style parallel-for over faces; SIMPLNX uses `ParallelDataAlgorithm` with `requireArraysInMemory`. No output impact (per-face independent writes). +5. **Wrong-phase bug** — the Phase-2 branch's `m_CrystalStructures[phase1]` guard and operator lookup were ported verbatim from the 2023 legacy code. **Fixed here** (`phase1`→`phase2`), matching the legacy 6.5.172 backport `1c96b3b8e`. + +*Material PRs since baseline:* #1631 (EbsdLib 3.0.0 + V&V cohort) added the Color Key option and first documented this bug with a `// KNOWN BUG` block; this V&V cycle removes that block and applies the fix. + +## Oracle + +*Class:* **1 (Analytical)**, with a **Class 4 (Invariant)** companion. + +*Applied:* IPF color is a closed-form function of (orientation, reference direction, Laue symmetry). At the standard stereographic-triangle corners the color is a pure primary, independent of implementation: cubic-high `<100>`→(255,0,0), cubic-high `<111>`→(0,0,255), hex-high c-axis→(255,0,0), hex-high basal→(0,255,0) (red channel exactly 0, since a basal direction sits at χ = χ_max so r = 1 − χ/χ_max = 0). A 4-face hand-built mesh with identity orientations and corner-aligned normals (Phase 1 = cubic, Phase 2 = hex) yields fully hand-derivable expected colors for both sides of every face. The Class 4 companion asserts the crispest bug signature: the hex (Phase-2) side of a basal face has red channel == 0, whereas the bug's cubic `<100>` lookup gives 255. + +*Encoded:* `test/ComputeFaceIPFColoringTest.cpp::"OrientationAnalysis::ComputeFaceIPFColoringFilter: Class 1 Oracle - mixed-phase analytical"` — 4 faces × (3 first + 3 second) = 24 color-byte assertions + 1 invariant assertion, all pass. Verified to **fail** on Face 0's second-color red channel when the `phase1`→`phase2` fix is reverted. + +*Caveat (surfaced by the legacy A/B):* the exact hex basal value `(0,255,0)` green is the **EbsdLib** assignment (canonical); legacy DREAM3D's older EbsdLib assigns the other basal corner (blue) to the same direction (deviation `-D2`). The convention-independent part of the oracle is the **red channel == 0** invariant (a basal direction is never the red c-axis corner), which both EbsdLib generations satisfy and which distinguishes the fixed hex result from the bug's cubic `<100>` red. The exact green is confirmed under **both EbsdLib 3.0.0 and 3.1.0** (the test passes unchanged on a from-source 3.1.0 build), so it is the canonical value, not a single-version artifact. + +*Second-engineer review:* *Pending — recommend an OA-domain engineer confirm the cubic `<100>`/`<111>` corner-primary derivations. (The D2 hex hue is resolved: EbsdLib is canonical and 3.0.0/3.1.0 agree on green; legacy blue is the deviation.)* + +## Code path coverage + +11 of 13 paths exercised. + +Source: `src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeFaceIPFColoring.cpp` (191 lines), plus `ComputeFaceIPFColoringFilter.cpp` preflight/execute. Logical phases: (a) preflight validation, (b) execute color-key routing, (c) per-face label→phase resolution, (d) Phase-1 first-color, (e) Phase-2 second-color. + +| # | Phase | Path | Test case | +|----|------------------|-----------------------------------------------------------------------------------|------------------------------------------------------------------------| +| 1 | (a) Preflight | face label/normal tuple mismatch → error `-2430` | `Invalid filter execution` — "Inconsistent face data tuple dimensions" | +| 2 | (a) Preflight | feature euler/phase tuple mismatch → error `-2432` | `Invalid filter execution` — "Inconsistent cell data tuple dimensions" | +| 3 | (a) Preflight | face labels array missing → error `-2431` | *Not directly tested. Guarded by `ArraySelectionParameter`; low-value.* | +| 4 | (b) Execute | Color Key 0/1/2 → TSL/PUCM/Nolze-Hielscher | `ColorKey choice reaches algorithm` — all three kinds | +| 5 | (b) Execute | Color Key out of [0,2] → error `-24340` | *Not directly tested. `ChoicesParameter` constrains the index; low-value.* | +| 6 | (c) Per-face | `feature1 > 0` → `phase1 = m_Phases[feature1]` | `Class 1 Oracle` — faces 0, 2, 3 | +| 7 | (c) Per-face | `feature1 <= 0` → `phase1 = 0` | `Class 1 Oracle` — face 1 (label −1) | +| 8 | (c) Per-face | `feature2 > 0` / `feature2 <= 0` → `phase2` set or 0 | `Class 1 Oracle` — faces 0/1 (valid), face 2 (−1) | +| 9 | (d) First color | `phase1 > 0` and crystal structure valid → cubic IPF color | `Class 1 Oracle` — faces 0, 2 (red), face 3 (blue) | +| 10 | (d) First color | `phase1 <= 0` → first color black | `Class 1 Oracle` — face 1 → (0,0,0) | +| 11 | (e) Second color | `phase2 > 0` and `CrystalStructures[phase2]` valid → **own-phase** IPF color (fix) | `Class 1 Oracle` — face 0 hex green, face 1 hex green, face 3 blue | +| 12 | (e) Second color | `phase2 <= 0` → second color black | `Class 1 Oracle` — face 2 → (0,0,0) | +| 13 | (d/e) | phase valid but `CrystalStructures[phase] >= LaueGroupEnd` → color left untouched | *Not directly tested. Low-value guard for a corrupt crystal-structure index.* | + +## Test inventory + +| Test case | Status | Notes | +|-----------|--------|-------| +| `Class 1 Oracle - mixed-phase analytical` | new-for-V&V | Hand-built 4-face cubic/hex mesh; 24 color-byte + 1 invariant assertions; analytical corner-primary oracle. Verified to fail when the bug is reintroduced. | +| `Valid filter execution` | retired | Compared against `SurfaceMeshFaceIPFColors` baked into `6_6_Small_IN100_GBCD` — the filter's own pre-fix output (circular oracle). It encoded the bug on `feature1`-invalid boundary faces, so the fix correctly broke it. Superseded by the Class 1 analytical test; real-data exercise retained by the Color-Key test below. | +| `Invalid filter execution` | kept | Two preflight tuple-mismatch sections (`-2430`, `-2432`). | +| `ColorKey choice reaches algorithm` | kept | Runs the filter on the full Small IN100 GBCD surface mesh three times (TSL/PUCM/NH) and asserts the outputs differ — real-data smoke test independent of any baked exemplar. | +| `SIMPL Backwards Compatibility` | kept | 6.4 and 6.5 SIMPL JSON → Arguments round-trip. | + +## Exemplar archive + +- **Archive:** None — Class 1 analytical fixtures are inlined in the test source. +- **SHA512:** n/a +- **Provenance:** No sidecar required (no archive owns the oracle). The retired circular-oracle situation is documented in `vv/provenance/ComputeFaceIPFColoringFilter-circular-oracle.md`. + +## Deviations from DREAM3D 6.5.171 + +Three-way binary comparison (6.5.171 / 6.5.172 / SIMPLNX) on a hand-built legacy-native cubic/hex mesh, plus SIMPLNX-vs-baked on the 756,474-face real mesh. Full write-up: `vv_work/face_ipf/legacy_comparison_summary.md`. + +- `ComputeFaceIPFColoringFilter-D1` — Phase-2 face side colored with Phase-1's Laue symmetry operator (and left black on `feature1`-invalid boundary faces). The #1635 bug; 6.5.171 reproduces it, 6.5.172 and SIMPLNX fix it. Empirically 120,000/756,474 faces affected on real data. See `vv/deviations/ComputeFaceIPFColoringFilter.md`. +- `ComputeFaceIPFColoringFilter-D2` — hex basal IPF hue differs between EbsdLib 3.0.0 (SIMPLNX, green) and legacy EbsdLib (6.5.171 & 6.5.172, blue); a library deviation affecting all hex IPF coloring, flagged for review. + +*Note:* the NX-written `6_6_Small_IN100_GBCD` surface mesh cannot be read by legacy `DataContainerReader` (both 6.5.171 and 6.5.172 fail identically — `DataArray` connectivity). The legacy-native fixture was authored with the `legacy_dream3d` writer to run the binary A/B. The NX→legacy geometry read gap is worth filing as its own issue. diff --git a/src/Plugins/OrientationAnalysis/vv/deviations/ComputeFaceIPFColoringFilter.md b/src/Plugins/OrientationAnalysis/vv/deviations/ComputeFaceIPFColoringFilter.md new file mode 100644 index 0000000000..5f7c1c6f33 --- /dev/null +++ b/src/Plugins/OrientationAnalysis/vv/deviations/ComputeFaceIPFColoringFilter.md @@ -0,0 +1,59 @@ +# Deviations from DREAM3D 6.5.171: ComputeFaceIPFColoringFilter + +This file lists every documented behavioral difference between this SIMPLNX filter and its DREAM3D 6.5.171 equivalent (`GenerateFaceIPFColoring`). + +Entries are referenced by stable ID (`ComputeFaceIPFColoringFilter-D`) from the V&V report and from public migration guidance. The ID is stable across renames; the Filter UUID field is the permanent cross-reference anchor. + +--- + +## ComputeFaceIPFColoringFilter-D1 + +| Field | Value | +|---|---| +| **Deviation ID** | `ComputeFaceIPFColoringFilter-D1` | +| **Filter UUID** | `30759600-7c02-4650-b5ca-e7036d6b568e` | +| **Status** | active | + +**Symptom:** On a surface-mesh face, the **Phase-2 (second) color** differs between SIMPLNX and DREAM3D 6.5.171 in two situations: (a) when the two adjacent features use **different Laue groups** (e.g., cubic on one side, hexagonal on the other), the 6.5.171 Phase-2 color is computed with the **wrong symmetry operator**; and (b) when the **first** face label is the invalid/exterior side (`feature1 ≤ 0`) while the second is a real feature, 6.5.171 leaves the Phase-2 color **black** instead of coloring it. SIMPLNX colors both correctly. + +**Root cause:** **Bug in 6.5.171.** In the Phase-2 branch of `CalculateFaceIPFColorsImpl::generate`, both the crystal-structure validity guard and the IPF-operator lookup indexed `m_CrystalStructures[phase1]` instead of `m_CrystalStructures[phase2]`, even though the same branch correctly read `feature2`'s Euler angles and the flipped face normal (`GenerateFaceIPFColoring.cpp:166,175` in 6.5.171; the same lines were ported verbatim into SIMPLNX `Algorithms/ComputeFaceIPFColoring.cpp:114,123`). Two consequences follow from the single wrong index: + +- *Mixed-phase faces:* `ops[m_CrystalStructures[phase1]]` applies Phase 1's symmetry operators to Phase 2's orientation — the output stays inside the IPF triangle so it looks valid but is materially wrong. +- *`feature1`-invalid boundary faces:* `phase1 = 0`, so the guard reads `m_CrystalStructures[0]` (the `UnknownCrystalStructure` sentinel, 999), which is `≥ LaueGroupEnd`; the guard fails and the second color is never written, leaving it black. This affects **even single-phase datasets** — which is why the legacy Small IN100 GBCD exemplar changed when the fix was applied. + +The fix (`phase1`→`phase2`) is confirmed against the Class 1 analytical oracle and matches the legacy 6.5.172 backport `1c96b3b8e`. + +**Empirical evidence — legacy binary A/B (hand-built mixed cubic/hex mesh).** On a legacy-native +4-face fixture (authored with the `legacy_dream3d` writer; see `vv_work/face_ipf/`), DREAM3D +6.5.171 reproduces the hand-derived *buggy* oracle exactly: the mixed-phase face's Phase-2 color +is cubic red `(255,0,0)` (Phase-1's operator on a hex feature) and the `feature1`-invalid boundary +face's Phase-2 color is black `(0,0,0)`. DREAM3D 6.5.172 and SIMPLNX both apply the hex operator on +those two faces (the fix). The two faces are exactly the ones predicted; cubic-side colors and the +cubic|cubic control face are identical across all three. + +**Empirical evidence — real data (756,474-face Small IN100 GBCD mesh).** SIMPLNX (fixed) vs the +buggy `SurfaceMeshFaceIPFColors` baked into the file (authentic 6.5.171-class output) differs on +**exactly 120,000 faces**, every one a `feature1 ≤ 0` boundary face, **zero** elsewhere (single-phase, +so only the boundary manifestation appears). Full write-up: `vv_work/face_ipf/legacy_comparison_summary.md`. + +**Affected users:** (a) Anyone coloring surface meshes of **multi-phase** microstructures where adjacent phases use different Laue groups — alpha/beta titanium, dual-phase steels, IN625 with inclusions. (b) **All** users (including single-phase) on exterior/boundary faces where the mesh ordered the exterior side as `feature1`; those faces' second color was silently black in 6.5.171. Users who only inspect the first color, or whose meshes order the real feature as `feature1`, are unaffected. + +**Recommendation:** **Trust SIMPLNX.** The 6.5.171 output was mathematically incorrect on the Phase-2 side; SIMPLNX applies each phase's own symmetry operators to its own orientation. DREAM3D 6.5.172 contains the same fix (`1c96b3b8e`); SIMPLNX and 6.5.172 agree on *which* operator to apply (the residual hex hue difference between them is a separate library deviation, `-D2` below). + +--- + +## ComputeFaceIPFColoringFilter-D2 + +| Field | Value | +|---|---| +| **Deviation ID** | `ComputeFaceIPFColoringFilter-D2` | +| **Filter UUID** | `30759600-7c02-4650-b5ca-e7036d6b568e` | +| **Status** | active | + +**Symptom:** For a **hexagonal** feature, the IPF color of a basal-plane reference direction differs between SIMPLNX and legacy DREAM3D (both 6.5.171 and 6.5.172): SIMPLNX produces **green `(0,255,0)`** where legacy produces **blue `(0,0,255)`** (and vice-versa for the other basal corner). Cubic coloring is identical. This is unrelated to the `-D1` wrong-phase bug — it shows up on every hex IPF color, in this filter and in the sibling cell-level `ComputeIPFColors`. + +**Root cause:** **Library.** SIMPLNX links EbsdLib 3.x; legacy DREAM3D links an older EbsdLib. The hexagonal Laue ops' assignment of the two basal standard-triangle corners (`[2-1-10]` vs `[10-10]`) to green/blue differs between those EbsdLib generations (equivalently, the cartesian a-axis convention / fundamental-sector reduction differs). Both assignments keep the direction in-gamut and give red-channel 0 (a basal direction is never the red c-axis corner); only the green↔blue hue assignment flips. Surfaced during the legacy A/B on the hand-built cubic/hex fixture (`vv_work/face_ipf/`): on the mixed-phase face, 6.5.171 = `(255,0,0)` (wrong phase), 6.5.172 = `(0,0,255)` (hex, legacy hue), SIMPLNX = `(0,255,0)` (hex, EbsdLib 3.x hue). Confirmed identical (`(0,255,0)`) under **both EbsdLib 3.0.0 and EbsdLib 3.1.0** — the Class 1 oracle test passes unchanged on a from-source 3.1.0 build (`NX-Com-Qt69-Vtk95-Rel-EbsdLib`). + +**Affected users:** Anyone comparing **hexagonal**-phase IPF colors between DREAM3D-NX and any legacy DREAM3D — face IPF colors and cell IPF colors alike. Cubic-only datasets are unaffected. + +**Recommendation:** **Trust SIMPLNX (EbsdLib).** EbsdLib is the canonical IPF-color authority, and its current output (3.0.0 and 3.1.0 agree: green) is the reference; legacy DREAM3D's blue is the deviation to migrate away from. The difference is a hue-assignment convention that lives in EbsdLib, not in this filter, so it applies uniformly to all hex IPF coloring (face and cell). Migration note for users: hexagonal IPF hues will change vs. legacy DREAM3D; this is expected and correct. diff --git a/src/Plugins/OrientationAnalysis/vv/provenance/ComputeFaceIPFColoringFilter-circular-oracle.md b/src/Plugins/OrientationAnalysis/vv/provenance/ComputeFaceIPFColoringFilter-circular-oracle.md new file mode 100644 index 0000000000..1162b49cde --- /dev/null +++ b/src/Plugins/OrientationAnalysis/vv/provenance/ComputeFaceIPFColoringFilter-circular-oracle.md @@ -0,0 +1,28 @@ +# Exemplar Provenance: ComputeFaceIPFColoringFilter (no archive — circular-oracle retirement) + +This filter's V&V uses a **Class 1 (Analytical)** oracle encoded inline in the test source, so it owns **no exemplar archive**. This sidecar exists only to record the retirement of the prior circular-oracle comparison, per the V&V policy's circular-oracle requirement. + +--- + +## Canonical oracle output + +| DataPath | Source of expected values | +|---|---| +| `/Data/NXFaceIPFColors 0` (first color) | Class 1 analytical — IPF standard-triangle corner primaries hand-derived in `test/ComputeFaceIPFColoringTest.cpp` ("Class 1 Oracle - mixed-phase analytical"). cubic `<100>`→(255,0,0), cubic `<111>`→(0,0,255). | +| `/Data/NXFaceIPFColors 1` (second color) | Class 1 analytical — hex c-axis→(255,0,0), hex basal→(0,255,0) (red channel 0); black where the adjacent feature label ≤ 0. | + +No Class 2/3/5 provenance block applies (Class 1 oracle lives in the test code). + +## Second-engineer oracle review + +- **Reviewer:** *pending* +- **Date:** *pending* +- **Note:** recommend an OA-domain engineer confirm the hex basal-corner color and the cubic corner-primary derivations. + +## Regenerated to fix a circular-oracle situation? + +**YES — comparison retired, not regenerated.** Before this V&V cycle the test `OrientationAnalysis::ComputeFaceIPFColoringFilter: Valid filter execution` compared the filter's output against a `SurfaceMeshFaceIPFColors` array baked into the shared archive `6_6_Small_IN100_GBCD.tar.gz`. That baked array was the filter's **own pre-fix output** — a textbook circular oracle: it encoded the issue #1635 bug (notably black Phase-2 colors on `feature1`-invalid boundary faces of the single-phase Small IN100 mesh), so applying the `phase1`→`phase2` fix correctly broke the test. + +Rather than regenerate the baked array from post-fix output (which would re-establish a circular oracle, and which is impractical because `6_6_Small_IN100_GBCD.tar.gz` is shared by other GBCD tests), the comparison was **retired** and replaced by the inlined Class 1 analytical oracle. Real-data exercise of the algorithm is retained by `ComputeFaceIPFColoringFilter: ColorKey choice reaches algorithm`, which runs the filter on the full Small IN100 GBCD surface mesh without depending on any baked exemplar. + +The shared archive `6_6_Small_IN100_GBCD.tar.gz` itself is **not** modified or retired — only this filter's use of its `SurfaceMeshFaceIPFColors` array as a correctness oracle. diff --git a/vv_work/face_ipf/build_fixture.py b/vv_work/face_ipf/build_fixture.py new file mode 100644 index 0000000000..5d1b15792e --- /dev/null +++ b/vv_work/face_ipf/build_fixture.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +"""Build a legacy-native triangle-mesh .dream3d fixture for the FaceIPF A/B. + +Mirrors the Class 1 analytical unit-test fixture: 4 faces, feature1=cubic / feature2=hex, +identity orientations, corner-aligned face normals. Both legacy PipelineRunner binaries +(6.5.171 buggy, 6.5.172 fixed) can read it. +""" +import sys +import numpy as np + +sys.path.insert(0, "/Users/mjackson/Workspace1/Claude_Support/skills/compare-legacy-dream3d") +from legacy_dream3d import D3DLegacyWriter + +OUT = "/Users/mjackson/Workspace3/simplnx/vv_work/face_ipf/fixture_legacy.dream3d" + +# 4 faces. Trivial connectivity (3 unique verts per triangle); positions are irrelevant to +# IPF coloring (the filter reads the FaceNormals array, not computed normals). +n_faces = 4 +vertices = np.zeros((n_faces * 3, 3), dtype=np.float32) +for i in range(n_faces * 3): + vertices[i] = [float(i), 0.0, 0.0] +triangles = np.arange(n_faces * 3, dtype=np.int64).reshape(n_faces, 3) + +face_labels = np.array([[1, 2], [-1, 2], [1, -1], [1, 3]], dtype=np.int32) +face_normals = np.array([[-1.0, 0.0, 0.0], + [-1.0, 0.0, 0.0], + [-1.0, 0.0, 0.0], + [1.0, 1.0, 1.0]], dtype=np.float64) + +# 4 features (index 0 unused); identity Euler angles +avg_euler = np.zeros((4, 3), dtype=np.float32) +phases = np.array([[0], [1], [2], [1]], dtype=np.int32) +# phase 0 unknown(999), phase 1 cubic-high(1), phase 2 hex-high(0) +crystal_structures = np.array([[999], [1], [0]], dtype=np.uint32) + +with D3DLegacyWriter(OUT) as w: + # Triangle DC: geometry + face data + w.add_triangle_geom("TriangleDataContainer", vertices, triangles) + w.add_attribute_matrix("TriangleDataContainer", "FaceData", (n_faces,), "Face") + w.add_data_array("TriangleDataContainer", "FaceData", "FaceLabels", face_labels, comp_dims=(2,)) + w.add_data_array("TriangleDataContainer", "FaceData", "FaceNormals", face_normals, comp_dims=(3,)) + + # Image DC: feature + ensemble data (GenerateFaceIPFColoring requires these in an Image geom) + w.add_image_geom("Small IN100", dims=(1, 1, 1)) + w.add_attribute_matrix("Small IN100", "Grain Data", (4,), "CellFeature") + w.add_data_array("Small IN100", "Grain Data", "AvgEulerAngles", avg_euler, comp_dims=(3,)) + w.add_data_array("Small IN100", "Grain Data", "Phases", phases, comp_dims=(1,)) + w.add_attribute_matrix("Small IN100", "Phase Data", (3,), "CellEnsemble") + w.add_data_array("Small IN100", "Phase Data", "CrystalStructures", crystal_structures, comp_dims=(1,)) + +print("wrote", OUT) diff --git a/vv_work/face_ipf/fx_6_5_171.json b/vv_work/face_ipf/fx_6_5_171.json new file mode 100644 index 0000000000..3afb2eb1ba --- /dev/null +++ b/vv_work/face_ipf/fx_6_5_171.json @@ -0,0 +1,170 @@ +{ + "0": { + "FilterVersion": "6.5.171", + "Filter_Enabled": true, + "Filter_Human_Label": "Read DREAM.3D Data File", + "Filter_Name": "DataContainerReader", + "Filter_Uuid": "{043cbde5-3878-5718-958f-ae75714df0df}", + "InputFile": "/Users/mjackson/Workspace3/simplnx/vv_work/face_ipf/fixture_legacy.dream3d", + "OverwriteExistingDataContainers": false, + "InputFileDataContainerArrayProxy": { + "Data Containers": [ + { + "Attribute Matricies": [ + { + "Data Arrays": [ + { + "Component Dimensions": [ + 2 + ], + "Flag": 2, + "Name": "FaceLabels", + "Object Type": "DataArray", + "Path": "/DataContainers/TriangleDataContainer/FaceData", + "Tuple Dimensions": [ + 1 + ], + "Version": 2 + }, + { + "Component Dimensions": [ + 3 + ], + "Flag": 2, + "Name": "FaceNormals", + "Object Type": "DataArray", + "Path": "/DataContainers/TriangleDataContainer/FaceData", + "Tuple Dimensions": [ + 1 + ], + "Version": 2 + } + ], + "Flag": 2, + "Name": "FaceData", + "Type": 2 + } + ], + "Flag": 2, + "Geometry": { + "Geometry_Type": 4, + "Geometry_Type_Name": "TriangleGeometry" + }, + "Name": "TriangleDataContainer" + }, + { + "Attribute Matricies": [ + { + "Data Arrays": [ + { + "Component Dimensions": [ + 3 + ], + "Flag": 2, + "Name": "AvgEulerAngles", + "Object Type": "DataArray", + "Path": "/DataContainers/Small IN100/Grain Data", + "Tuple Dimensions": [ + 1 + ], + "Version": 2 + }, + { + "Component Dimensions": [ + 1 + ], + "Flag": 2, + "Name": "Phases", + "Object Type": "DataArray", + "Path": "/DataContainers/Small IN100/Grain Data", + "Tuple Dimensions": [ + 1 + ], + "Version": 2 + } + ], + "Flag": 2, + "Name": "Grain Data", + "Type": 7 + }, + { + "Data Arrays": [ + { + "Component Dimensions": [ + 1 + ], + "Flag": 2, + "Name": "CrystalStructures", + "Object Type": "DataArray", + "Path": "/DataContainers/Small IN100/Phase Data", + "Tuple Dimensions": [ + 1 + ], + "Version": 2 + } + ], + "Flag": 2, + "Name": "Phase Data", + "Type": 11 + } + ], + "Flag": 2, + "Geometry": { + "Geometry_Type": 0, + "Geometry_Type_Name": "ImageGeometry" + }, + "Name": "Small IN100" + } + ], + "Version": 6 + } + }, + "1": { + "FilterVersion": "6.5.171", + "Filter_Enabled": true, + "Filter_Human_Label": "Generate IPF Colors (Face)", + "Filter_Name": "GenerateFaceIPFColoring", + "Filter_Uuid": "{0a121e03-3922-5c29-962d-40d88653f4b6}", + "SurfaceMeshFaceLabelsArrayPath": { + "Data Container Name": "TriangleDataContainer", + "Attribute Matrix Name": "FaceData", + "Data Array Name": "FaceLabels" + }, + "SurfaceMeshFaceNormalsArrayPath": { + "Data Container Name": "TriangleDataContainer", + "Attribute Matrix Name": "FaceData", + "Data Array Name": "FaceNormals" + }, + "FeatureEulerAnglesArrayPath": { + "Data Container Name": "Small IN100", + "Attribute Matrix Name": "Grain Data", + "Data Array Name": "AvgEulerAngles" + }, + "FeaturePhasesArrayPath": { + "Data Container Name": "Small IN100", + "Attribute Matrix Name": "Grain Data", + "Data Array Name": "Phases" + }, + "CrystalStructuresArrayPath": { + "Data Container Name": "Small IN100", + "Attribute Matrix Name": "Phase Data", + "Data Array Name": "CrystalStructures" + }, + "SurfaceMeshFaceIPFColorsArrayName": "NXCompare_IPFColors" + }, + "2": { + "FilterVersion": "6.5.171", + "Filter_Enabled": true, + "Filter_Human_Label": "Write DREAM.3D Data File", + "Filter_Name": "DataContainerWriter", + "Filter_Uuid": "{3fcd4c43-9d75-5b86-aad4-4441bc914f37}", + "OutputFile": "/Users/mjackson/Workspace3/simplnx/vv_work/face_ipf/fx_out_6_5_171.dream3d", + "WriteTimeSeries": false, + "WriteXdmfFile": false + }, + "PipelineBuilder": { + "Name": "FaceIPF legacy AB 6_5_171", + "Number_Filters": 3, + "Version": 6 + } +} \ No newline at end of file diff --git a/vv_work/face_ipf/fx_6_5_172.json b/vv_work/face_ipf/fx_6_5_172.json new file mode 100644 index 0000000000..22fbbbcfb2 --- /dev/null +++ b/vv_work/face_ipf/fx_6_5_172.json @@ -0,0 +1,170 @@ +{ + "0": { + "FilterVersion": "6.5.171", + "Filter_Enabled": true, + "Filter_Human_Label": "Read DREAM.3D Data File", + "Filter_Name": "DataContainerReader", + "Filter_Uuid": "{043cbde5-3878-5718-958f-ae75714df0df}", + "InputFile": "/Users/mjackson/Workspace3/simplnx/vv_work/face_ipf/fixture_legacy.dream3d", + "OverwriteExistingDataContainers": false, + "InputFileDataContainerArrayProxy": { + "Data Containers": [ + { + "Attribute Matricies": [ + { + "Data Arrays": [ + { + "Component Dimensions": [ + 2 + ], + "Flag": 2, + "Name": "FaceLabels", + "Object Type": "DataArray", + "Path": "/DataContainers/TriangleDataContainer/FaceData", + "Tuple Dimensions": [ + 1 + ], + "Version": 2 + }, + { + "Component Dimensions": [ + 3 + ], + "Flag": 2, + "Name": "FaceNormals", + "Object Type": "DataArray", + "Path": "/DataContainers/TriangleDataContainer/FaceData", + "Tuple Dimensions": [ + 1 + ], + "Version": 2 + } + ], + "Flag": 2, + "Name": "FaceData", + "Type": 2 + } + ], + "Flag": 2, + "Geometry": { + "Geometry_Type": 4, + "Geometry_Type_Name": "TriangleGeometry" + }, + "Name": "TriangleDataContainer" + }, + { + "Attribute Matricies": [ + { + "Data Arrays": [ + { + "Component Dimensions": [ + 3 + ], + "Flag": 2, + "Name": "AvgEulerAngles", + "Object Type": "DataArray", + "Path": "/DataContainers/Small IN100/Grain Data", + "Tuple Dimensions": [ + 1 + ], + "Version": 2 + }, + { + "Component Dimensions": [ + 1 + ], + "Flag": 2, + "Name": "Phases", + "Object Type": "DataArray", + "Path": "/DataContainers/Small IN100/Grain Data", + "Tuple Dimensions": [ + 1 + ], + "Version": 2 + } + ], + "Flag": 2, + "Name": "Grain Data", + "Type": 7 + }, + { + "Data Arrays": [ + { + "Component Dimensions": [ + 1 + ], + "Flag": 2, + "Name": "CrystalStructures", + "Object Type": "DataArray", + "Path": "/DataContainers/Small IN100/Phase Data", + "Tuple Dimensions": [ + 1 + ], + "Version": 2 + } + ], + "Flag": 2, + "Name": "Phase Data", + "Type": 11 + } + ], + "Flag": 2, + "Geometry": { + "Geometry_Type": 0, + "Geometry_Type_Name": "ImageGeometry" + }, + "Name": "Small IN100" + } + ], + "Version": 6 + } + }, + "1": { + "FilterVersion": "6.5.171", + "Filter_Enabled": true, + "Filter_Human_Label": "Generate IPF Colors (Face)", + "Filter_Name": "GenerateFaceIPFColoring", + "Filter_Uuid": "{0a121e03-3922-5c29-962d-40d88653f4b6}", + "SurfaceMeshFaceLabelsArrayPath": { + "Data Container Name": "TriangleDataContainer", + "Attribute Matrix Name": "FaceData", + "Data Array Name": "FaceLabels" + }, + "SurfaceMeshFaceNormalsArrayPath": { + "Data Container Name": "TriangleDataContainer", + "Attribute Matrix Name": "FaceData", + "Data Array Name": "FaceNormals" + }, + "FeatureEulerAnglesArrayPath": { + "Data Container Name": "Small IN100", + "Attribute Matrix Name": "Grain Data", + "Data Array Name": "AvgEulerAngles" + }, + "FeaturePhasesArrayPath": { + "Data Container Name": "Small IN100", + "Attribute Matrix Name": "Grain Data", + "Data Array Name": "Phases" + }, + "CrystalStructuresArrayPath": { + "Data Container Name": "Small IN100", + "Attribute Matrix Name": "Phase Data", + "Data Array Name": "CrystalStructures" + }, + "SurfaceMeshFaceIPFColorsArrayName": "NXCompare_IPFColors" + }, + "2": { + "FilterVersion": "6.5.171", + "Filter_Enabled": true, + "Filter_Human_Label": "Write DREAM.3D Data File", + "Filter_Name": "DataContainerWriter", + "Filter_Uuid": "{3fcd4c43-9d75-5b86-aad4-4441bc914f37}", + "OutputFile": "/Users/mjackson/Workspace3/simplnx/vv_work/face_ipf/fx_out_6_5_172.dream3d", + "WriteTimeSeries": false, + "WriteXdmfFile": false + }, + "PipelineBuilder": { + "Name": "FaceIPF legacy AB 6_5_172", + "Number_Filters": 3, + "Version": 6 + } +} \ No newline at end of file diff --git a/vv_work/face_ipf/gen_nx_pipeline.py b/vv_work/face_ipf/gen_nx_pipeline.py new file mode 100644 index 0000000000..2b6f0f6568 --- /dev/null +++ b/vv_work/face_ipf/gen_nx_pipeline.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +import json + +IN = "/Users/mjackson/Workspace1/DREAM3D_Data/TestFiles/6_6_Small_IN100_GBCD/6_6_Small_IN100_GBCD.dream3d" +OUT = "/Users/mjackson/Workspace3/simplnx/vv_work/face_ipf/out_nx_fixed.dream3d" + + +def arg(v): + return {"value": v, "version": 1} + + +pipeline = { + "name": "FaceIPF NX fixed", + "pipeline": [ + { + "args": {"import_data_object": arg({"file_path": IN, "data_paths": None})}, + "comments": "", + "filter": {"name": "nx::core::ReadDREAM3DFilter", "uuid": "0dbd31c7-19e0-4077-83ef-f4a6459a0e2d"}, + "isDisabled": False, + }, + { + "args": { + "surface_mesh_face_labels_array_path": arg("TriangleDataContainer/FaceData/FaceLabels"), + "surface_mesh_face_normals_array_path": arg("TriangleDataContainer/FaceData/FaceNormals"), + "feature_euler_angles_array_path": arg("Small IN100/Grain Data/AvgEulerAngles"), + "feature_phases_array_path": arg("Small IN100/Grain Data/Phases"), + "crystal_structures_array_path": arg("Small IN100/Phase Data/CrystalStructures"), + "first_face_ipf_colors_array_name": arg("NX_FaceIPF_First"), + "second_face_ipf_colors_array_name": arg("NX_FaceIPF_Second"), + "color_key_index": arg(0), + }, + "comments": "", + "filter": {"name": "nx::core::ComputeFaceIPFColoringFilter", "uuid": "30759600-7c02-4650-b5ca-e7036d6b568e"}, + "isDisabled": False, + }, + { + "args": {"export_file_path": arg(OUT), "write_xdmf_file": arg(False)}, + "comments": "", + "filter": {"name": "nx::core::WriteDREAM3DFilter", "uuid": "b3a95784-2ced-41ec-8d3d-0242ac130003"}, + "isDisabled": False, + }, + ], + "version": 1, +} + +with open("/Users/mjackson/Workspace3/simplnx/vv_work/face_ipf/nx_FaceIPF.d3dpipeline", "w") as fh: + json.dump(pipeline, fh, indent=2) +print("wrote nx_FaceIPF.d3dpipeline") diff --git a/vv_work/face_ipf/gen_pipeline.py b/vv_work/face_ipf/gen_pipeline.py new file mode 100644 index 0000000000..66e9a80c10 --- /dev/null +++ b/vv_work/face_ipf/gen_pipeline.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python3 +"""Generate SIMPL pipelines for the GenerateFaceIPFColoring legacy A/B comparison.""" +import json +import sys + +IN = "/Users/mjackson/Workspace1/DREAM3D_Data/TestFiles/6_6_Small_IN100_GBCD/6_6_Small_IN100_GBCD.dream3d" +OUT_NAME = "NXCompare_IPFColors" # created 6-component array + + +def da(name, path, obj_type, comps): + return { + "Component Dimensions": [comps], + "Flag": 2, + "Name": name, + "Object Type": obj_type, + "Path": path, + "Tuple Dimensions": [1], + "Version": 2, + } + + +def reader(input_file): + proxy = { + "Data Containers": [ + { + "Attribute Matricies": [ + { + "Data Arrays": [ + da("FaceLabels", "/DataContainers/TriangleDataContainer/FaceData", "DataArray", 2), + da("FaceNormals", "/DataContainers/TriangleDataContainer/FaceData", "DataArray", 3), + ], + "Flag": 2, + "Name": "FaceData", + "Type": 2, # Face + } + ], + "Flag": 2, + "Geometry": {"Geometry_Type": 4, "Geometry_Type_Name": "TriangleGeometry"}, + "Name": "TriangleDataContainer", + }, + { + "Attribute Matricies": [ + { + "Data Arrays": [ + da("AvgEulerAngles", "/DataContainers/Small IN100/Grain Data", "DataArray", 3), + da("Phases", "/DataContainers/Small IN100/Grain Data", "DataArray", 1), + ], + "Flag": 2, + "Name": "Grain Data", + "Type": 7, # CellFeature + }, + { + "Data Arrays": [ + da("CrystalStructures", "/DataContainers/Small IN100/Phase Data", "DataArray", 1), + ], + "Flag": 2, + "Name": "Phase Data", + "Type": 11, # CellEnsemble + }, + ], + "Flag": 2, + "Geometry": {"Geometry_Type": 0, "Geometry_Type_Name": "ImageGeometry"}, + "Name": "Small IN100", + }, + ], + "Version": 6, + } + return { + "FilterVersion": "6.5.171", + "Filter_Enabled": True, + "Filter_Human_Label": "Read DREAM.3D Data File", + "Filter_Name": "DataContainerReader", + "Filter_Uuid": "{043cbde5-3878-5718-958f-ae75714df0df}", + "InputFile": input_file, + "OverwriteExistingDataContainers": False, + "InputFileDataContainerArrayProxy": proxy, + } + + +def dap(dc, am, name): + return {"Data Container Name": dc, "Attribute Matrix Name": am, "Data Array Name": name} + + +def ipf_filter(): + return { + "FilterVersion": "6.5.171", + "Filter_Enabled": True, + "Filter_Human_Label": "Generate IPF Colors (Face)", + "Filter_Name": "GenerateFaceIPFColoring", + "Filter_Uuid": "{0a121e03-3922-5c29-962d-40d88653f4b6}", + "SurfaceMeshFaceLabelsArrayPath": dap("TriangleDataContainer", "FaceData", "FaceLabels"), + "SurfaceMeshFaceNormalsArrayPath": dap("TriangleDataContainer", "FaceData", "FaceNormals"), + "FeatureEulerAnglesArrayPath": dap("Small IN100", "Grain Data", "AvgEulerAngles"), + "FeaturePhasesArrayPath": dap("Small IN100", "Grain Data", "Phases"), + "CrystalStructuresArrayPath": dap("Small IN100", "Phase Data", "CrystalStructures"), + "SurfaceMeshFaceIPFColorsArrayName": OUT_NAME, + } + + +def writer(output_file): + return { + "FilterVersion": "6.5.171", + "Filter_Enabled": True, + "Filter_Human_Label": "Write DREAM.3D Data File", + "Filter_Name": "DataContainerWriter", + "Filter_Uuid": "{3fcd4c43-9d75-5b86-aad4-4441bc914f37}", + "OutputFile": output_file, + "WriteTimeSeries": False, + "WriteXdmfFile": False, + } + + +def build(output_file, name): + return { + "0": reader(IN), + "1": ipf_filter(), + "2": writer(output_file), + "PipelineBuilder": {"Name": name, "Number_Filters": 3, "Version": 6}, + } + + +if __name__ == "__main__": + out_dir = "/Users/mjackson/Workspace3/simplnx/vv_work/face_ipf" + for ver in ("6_5_171", "6_5_172"): + pl = build(f"{out_dir}/out_{ver}.dream3d", f"FaceIPF legacy AB {ver}") + with open(f"{out_dir}/legacy_FaceIPF_{ver}.json", "w") as fh: + json.dump(pl, fh, indent=2) + print(f"wrote legacy_FaceIPF_{ver}.json") diff --git a/vv_work/face_ipf/legacy_FaceIPF_6_5_171.json b/vv_work/face_ipf/legacy_FaceIPF_6_5_171.json new file mode 100644 index 0000000000..f0a3793287 --- /dev/null +++ b/vv_work/face_ipf/legacy_FaceIPF_6_5_171.json @@ -0,0 +1,170 @@ +{ + "0": { + "FilterVersion": "6.5.171", + "Filter_Enabled": true, + "Filter_Human_Label": "Read DREAM.3D Data File", + "Filter_Name": "DataContainerReader", + "Filter_Uuid": "{043cbde5-3878-5718-958f-ae75714df0df}", + "InputFile": "/Users/mjackson/Workspace3/simplnx/vv_work/face_ipf/input_legacy_readable.dream3d", + "OverwriteExistingDataContainers": false, + "InputFileDataContainerArrayProxy": { + "Data Containers": [ + { + "Attribute Matricies": [ + { + "Data Arrays": [ + { + "Component Dimensions": [ + 2 + ], + "Flag": 2, + "Name": "FaceLabels", + "Object Type": "DataArray", + "Path": "/DataContainers/TriangleDataContainer/FaceData", + "Tuple Dimensions": [ + 1 + ], + "Version": 2 + }, + { + "Component Dimensions": [ + 3 + ], + "Flag": 2, + "Name": "FaceNormals", + "Object Type": "DataArray", + "Path": "/DataContainers/TriangleDataContainer/FaceData", + "Tuple Dimensions": [ + 1 + ], + "Version": 2 + } + ], + "Flag": 2, + "Name": "FaceData", + "Type": 2 + } + ], + "Flag": 2, + "Geometry": { + "Geometry_Type": 4, + "Geometry_Type_Name": "TriangleGeometry" + }, + "Name": "TriangleDataContainer" + }, + { + "Attribute Matricies": [ + { + "Data Arrays": [ + { + "Component Dimensions": [ + 3 + ], + "Flag": 2, + "Name": "AvgEulerAngles", + "Object Type": "DataArray", + "Path": "/DataContainers/Small IN100/Grain Data", + "Tuple Dimensions": [ + 1 + ], + "Version": 2 + }, + { + "Component Dimensions": [ + 1 + ], + "Flag": 2, + "Name": "Phases", + "Object Type": "DataArray", + "Path": "/DataContainers/Small IN100/Grain Data", + "Tuple Dimensions": [ + 1 + ], + "Version": 2 + } + ], + "Flag": 2, + "Name": "Grain Data", + "Type": 7 + }, + { + "Data Arrays": [ + { + "Component Dimensions": [ + 1 + ], + "Flag": 2, + "Name": "CrystalStructures", + "Object Type": "DataArray", + "Path": "/DataContainers/Small IN100/Phase Data", + "Tuple Dimensions": [ + 1 + ], + "Version": 2 + } + ], + "Flag": 2, + "Name": "Phase Data", + "Type": 11 + } + ], + "Flag": 2, + "Geometry": { + "Geometry_Type": 0, + "Geometry_Type_Name": "ImageGeometry" + }, + "Name": "Small IN100" + } + ], + "Version": 6 + } + }, + "1": { + "FilterVersion": "6.5.171", + "Filter_Enabled": true, + "Filter_Human_Label": "Generate IPF Colors (Face)", + "Filter_Name": "GenerateFaceIPFColoring", + "Filter_Uuid": "{0a121e03-3922-5c29-962d-40d88653f4b6}", + "SurfaceMeshFaceLabelsArrayPath": { + "Data Container Name": "TriangleDataContainer", + "Attribute Matrix Name": "FaceData", + "Data Array Name": "FaceLabels" + }, + "SurfaceMeshFaceNormalsArrayPath": { + "Data Container Name": "TriangleDataContainer", + "Attribute Matrix Name": "FaceData", + "Data Array Name": "FaceNormals" + }, + "FeatureEulerAnglesArrayPath": { + "Data Container Name": "Small IN100", + "Attribute Matrix Name": "Grain Data", + "Data Array Name": "AvgEulerAngles" + }, + "FeaturePhasesArrayPath": { + "Data Container Name": "Small IN100", + "Attribute Matrix Name": "Grain Data", + "Data Array Name": "Phases" + }, + "CrystalStructuresArrayPath": { + "Data Container Name": "Small IN100", + "Attribute Matrix Name": "Phase Data", + "Data Array Name": "CrystalStructures" + }, + "SurfaceMeshFaceIPFColorsArrayName": "NXCompare_IPFColors" + }, + "2": { + "FilterVersion": "6.5.171", + "Filter_Enabled": true, + "Filter_Human_Label": "Write DREAM.3D Data File", + "Filter_Name": "DataContainerWriter", + "Filter_Uuid": "{3fcd4c43-9d75-5b86-aad4-4441bc914f37}", + "OutputFile": "/Users/mjackson/Workspace3/simplnx/vv_work/face_ipf/out_6_5_171.dream3d", + "WriteTimeSeries": false, + "WriteXdmfFile": false + }, + "PipelineBuilder": { + "Name": "FaceIPF legacy AB 6_5_171", + "Number_Filters": 3, + "Version": 6 + } +} \ No newline at end of file diff --git a/vv_work/face_ipf/legacy_FaceIPF_6_5_172.json b/vv_work/face_ipf/legacy_FaceIPF_6_5_172.json new file mode 100644 index 0000000000..df6868cfab --- /dev/null +++ b/vv_work/face_ipf/legacy_FaceIPF_6_5_172.json @@ -0,0 +1,170 @@ +{ + "0": { + "FilterVersion": "6.5.171", + "Filter_Enabled": true, + "Filter_Human_Label": "Read DREAM.3D Data File", + "Filter_Name": "DataContainerReader", + "Filter_Uuid": "{043cbde5-3878-5718-958f-ae75714df0df}", + "InputFile": "/Users/mjackson/Workspace3/simplnx/vv_work/face_ipf/input_legacy_readable.dream3d", + "OverwriteExistingDataContainers": false, + "InputFileDataContainerArrayProxy": { + "Data Containers": [ + { + "Attribute Matricies": [ + { + "Data Arrays": [ + { + "Component Dimensions": [ + 2 + ], + "Flag": 2, + "Name": "FaceLabels", + "Object Type": "DataArray", + "Path": "/DataContainers/TriangleDataContainer/FaceData", + "Tuple Dimensions": [ + 1 + ], + "Version": 2 + }, + { + "Component Dimensions": [ + 3 + ], + "Flag": 2, + "Name": "FaceNormals", + "Object Type": "DataArray", + "Path": "/DataContainers/TriangleDataContainer/FaceData", + "Tuple Dimensions": [ + 1 + ], + "Version": 2 + } + ], + "Flag": 2, + "Name": "FaceData", + "Type": 2 + } + ], + "Flag": 2, + "Geometry": { + "Geometry_Type": 4, + "Geometry_Type_Name": "TriangleGeometry" + }, + "Name": "TriangleDataContainer" + }, + { + "Attribute Matricies": [ + { + "Data Arrays": [ + { + "Component Dimensions": [ + 3 + ], + "Flag": 2, + "Name": "AvgEulerAngles", + "Object Type": "DataArray", + "Path": "/DataContainers/Small IN100/Grain Data", + "Tuple Dimensions": [ + 1 + ], + "Version": 2 + }, + { + "Component Dimensions": [ + 1 + ], + "Flag": 2, + "Name": "Phases", + "Object Type": "DataArray", + "Path": "/DataContainers/Small IN100/Grain Data", + "Tuple Dimensions": [ + 1 + ], + "Version": 2 + } + ], + "Flag": 2, + "Name": "Grain Data", + "Type": 7 + }, + { + "Data Arrays": [ + { + "Component Dimensions": [ + 1 + ], + "Flag": 2, + "Name": "CrystalStructures", + "Object Type": "DataArray", + "Path": "/DataContainers/Small IN100/Phase Data", + "Tuple Dimensions": [ + 1 + ], + "Version": 2 + } + ], + "Flag": 2, + "Name": "Phase Data", + "Type": 11 + } + ], + "Flag": 2, + "Geometry": { + "Geometry_Type": 0, + "Geometry_Type_Name": "ImageGeometry" + }, + "Name": "Small IN100" + } + ], + "Version": 6 + } + }, + "1": { + "FilterVersion": "6.5.171", + "Filter_Enabled": true, + "Filter_Human_Label": "Generate IPF Colors (Face)", + "Filter_Name": "GenerateFaceIPFColoring", + "Filter_Uuid": "{0a121e03-3922-5c29-962d-40d88653f4b6}", + "SurfaceMeshFaceLabelsArrayPath": { + "Data Container Name": "TriangleDataContainer", + "Attribute Matrix Name": "FaceData", + "Data Array Name": "FaceLabels" + }, + "SurfaceMeshFaceNormalsArrayPath": { + "Data Container Name": "TriangleDataContainer", + "Attribute Matrix Name": "FaceData", + "Data Array Name": "FaceNormals" + }, + "FeatureEulerAnglesArrayPath": { + "Data Container Name": "Small IN100", + "Attribute Matrix Name": "Grain Data", + "Data Array Name": "AvgEulerAngles" + }, + "FeaturePhasesArrayPath": { + "Data Container Name": "Small IN100", + "Attribute Matrix Name": "Grain Data", + "Data Array Name": "Phases" + }, + "CrystalStructuresArrayPath": { + "Data Container Name": "Small IN100", + "Attribute Matrix Name": "Phase Data", + "Data Array Name": "CrystalStructures" + }, + "SurfaceMeshFaceIPFColorsArrayName": "NXCompare_IPFColors" + }, + "2": { + "FilterVersion": "6.5.171", + "Filter_Enabled": true, + "Filter_Human_Label": "Write DREAM.3D Data File", + "Filter_Name": "DataContainerWriter", + "Filter_Uuid": "{3fcd4c43-9d75-5b86-aad4-4441bc914f37}", + "OutputFile": "/Users/mjackson/Workspace3/simplnx/vv_work/face_ipf/out_6_5_172.dream3d", + "WriteTimeSeries": false, + "WriteXdmfFile": false + }, + "PipelineBuilder": { + "Name": "FaceIPF legacy AB 6_5_172", + "Number_Filters": 3, + "Version": 6 + } +} \ No newline at end of file diff --git a/vv_work/face_ipf/legacy_comparison_summary.md b/vv_work/face_ipf/legacy_comparison_summary.md new file mode 100644 index 0000000000..69f395face --- /dev/null +++ b/vv_work/face_ipf/legacy_comparison_summary.md @@ -0,0 +1,82 @@ +# Legacy Comparison: ComputeFaceIPFColoring (issue #1635) + +Date: 2026-06-30 + +## Goal + +Three-way A/B of the Phase-2 face-IPF coloring: SIMPLNX (fixed) vs DREAM3D 6.5.171 (buggy) vs DREAM3D 6.5.172 (fixed). + +## Environment + +- **6.5.171 (buggy):** `/Users/mjackson/Applications/DREAM3D.app/Contents/bin/PipelineRunner` (Apr 2023) +- **6.5.172 (fixed):** `/Users/mjackson/Workspace3/6.5.172/DREAM3D-Build/D3D-Rel-Qt515-6_5_171/Bin/PipelineRunner` +- **SIMPLNX (fixed):** `…/DREAM3D-Build/NX-Com-Qt69-Vtk96-Rel/Bin/nxrunner` + +## Inputs + +1. **`fixture_legacy.dream3d`** — a legacy-native 4-face triangle mesh (built with the + `legacy_dream3d` writer extended with `add_triangle_geom`), mirroring the Class 1 analytical + unit-test fixture: feature1 = cubic (phase 1), feature2 = hex (phase 2), identity orientations, + corner-aligned face normals. Both legacy `PipelineRunner` binaries read it without error. + - F0 `(1,2)` cubic|hex (mixed-phase), F1 `(-1,2)` --|hex (boundary), F2 `(1,-1)` cubic|--, F3 `(1,3)` cubic|cubic +2. **`6_6_Small_IN100_GBCD.dream3d`** — the real 756,474-face mesh, used for the SIMPLNX-vs-baked + real-data check (legacy binaries cannot read its NX-written geometry; see Note A). + +## Result 1 — three-way on the legacy-native fixture (the headline) + +Second (Phase-2 side) color per face. Cubic first-side colors and the cubic|cubic face (F3) agree across all three. + +| Face | kind | 6.5.171 (buggy) | 6.5.172 (fixed) | SIMPLNX (fixed) | +|---|---|---|---|---| +| F0 | cubic\|hex (mixed) | **(255,0,0)** wrong-phase cubic | (0,0,255) hex | (0,255,0) hex | +| F1 | --\|hex (boundary) | **(0,0,0)** black | (0,0,255) hex | (0,255,0) hex | +| F2 | cubic\|-- | (0,0,0) black | (0,0,0) black | (0,0,0) black | +| F3 | cubic\|cubic | (0,0,255) | (0,0,255) | (0,0,255) | + +Two independent deviations fall out cleanly: + +- **D1 — the #1635 bug (root cause: bug in 6.5.171).** On F0 and F1, 6.5.171 colors the Phase-2 + side with Phase-1's symmetry (F0 → cubic red instead of a hex color) or leaves it black + (F1, `feature1 ≤ 0`). 6.5.172 **and** SIMPLNX both apply the hex operator. 6.5.171's output + matches the hand-derived *buggy* oracle exactly. **The fix is confirmed on real legacy binaries.** +- **D2 — EbsdLib hex basal hue (root cause: library).** Where both fixed implementations apply + the hex operator (F0, F1), legacy EbsdLib (6.5.172) produces **blue (0,0,255)** and EbsdLib + 3.0.0 (SIMPLNX) produces **green (0,255,0)** for the same hex basal direction. Cubic coloring + is identical across all three. This is independent of #1635 — it is the hex IPF basal-corner + hue assignment changing between EbsdLib versions. Both colors have red-channel 0 (a basal + direction is never the red c-axis corner), so the convention-independent invariant holds for + both; only the green/blue hue assignment differs. + +## Result 2 — SIMPLNX (fixed) vs baked buggy baseline on the real 756,474-face mesh + +The baked `SurfaceMeshFaceIPFColors` in `6_6_Small_IN100_GBCD.dream3d` is authentic buggy +(6.5.171-class) output. SIMPLNX-fixed vs that baseline differs on **exactly 120,000 faces**, all +of them `feature1 ≤ 0` boundary faces (the D1 boundary manifestation), **0** elsewhere, **0** on +the 636,474 interior faces. (Single-phase data, so no mixed-phase or hex faces appear — D2 does +not show here.) + +## Note A — NX→legacy geometry incompatibility (separate finding) + +The NX-written `6_6_Small_IN100_GBCD.dream3d` cannot be read by either legacy `PipelineRunner` +(both 6.5.171 and 6.5.172 fail identically: `SharedTriList` loads as 0 tuples → `-10200`). Root +cause: DREAM3D-NX writes the triangle connectivity as `DataArray`, which legacy +`H5DataArrayReader` does not recognize (legacy `SharedTriList = DataArray`); the legacy +reader does not reconstruct the NX `_SIMPL_GEOMETRY` group. This is why the fixture in Input 1 was +authored natively in legacy format. Worth filing as its own NX-export issue; unrelated to the IPF filter. + +## Conclusion + +- **D1 (#1635):** confirmed and fixed. Trust SIMPLNX / 6.5.172. 6.5.171 was wrong. +- **D2 (hex hue):** library deviation between EbsdLib (green) and legacy EbsdLib (blue); affects + all hex IPF coloring (face and cell). **Resolved:** EbsdLib is the canonical IPF authority and + EbsdLib 3.0.0 and 3.1.0 agree on green (the Class 1 oracle test passes unchanged on a from-source + EbsdLib 3.1.0 build, `NX-Com-Qt69-Vtk95-Rel-EbsdLib`); legacy DREAM3D's blue is the deviation. + Trust SIMPLNX; expect hex IPF hues to change vs. legacy. +- The Class 1 oracle's exact hex value (green) is the canonical EbsdLib value (3.0.0 == 3.1.0); the + convention-independent assertion is red-channel == 0, which every version satisfies. + +## Fixes Applied + +- SIMPLNX `Algorithms/ComputeFaceIPFColoring.cpp:114,123` `phase1`→`phase2`; `KNOWN BUG` block + removed. No legacy code modified (6.5.172 already carried the D1 fix; D2 is not fixed here). +- `legacy_dream3d` writer extended with `add_triangle_geom` (Gate-1 validated). diff --git a/vv_work/face_ipf/nx_FaceIPF.d3dpipeline b/vv_work/face_ipf/nx_FaceIPF.d3dpipeline new file mode 100644 index 0000000000..512f620c3e --- /dev/null +++ b/vv_work/face_ipf/nx_FaceIPF.d3dpipeline @@ -0,0 +1,83 @@ +{ + "name": "FaceIPF NX fixed", + "pipeline": [ + { + "args": { + "import_data_object": { + "value": { + "file_path": "/Users/mjackson/Workspace1/DREAM3D_Data/TestFiles/6_6_Small_IN100_GBCD/6_6_Small_IN100_GBCD.dream3d", + "data_paths": null + }, + "version": 1 + } + }, + "comments": "", + "filter": { + "name": "nx::core::ReadDREAM3DFilter", + "uuid": "0dbd31c7-19e0-4077-83ef-f4a6459a0e2d" + }, + "isDisabled": false + }, + { + "args": { + "surface_mesh_face_labels_array_path": { + "value": "TriangleDataContainer/FaceData/FaceLabels", + "version": 1 + }, + "surface_mesh_face_normals_array_path": { + "value": "TriangleDataContainer/FaceData/FaceNormals", + "version": 1 + }, + "feature_euler_angles_array_path": { + "value": "Small IN100/Grain Data/AvgEulerAngles", + "version": 1 + }, + "feature_phases_array_path": { + "value": "Small IN100/Grain Data/Phases", + "version": 1 + }, + "crystal_structures_array_path": { + "value": "Small IN100/Phase Data/CrystalStructures", + "version": 1 + }, + "first_face_ipf_colors_array_name": { + "value": "NX_FaceIPF_First", + "version": 1 + }, + "second_face_ipf_colors_array_name": { + "value": "NX_FaceIPF_Second", + "version": 1 + }, + "color_key_index": { + "value": 0, + "version": 1 + } + }, + "comments": "", + "filter": { + "name": "nx::core::ComputeFaceIPFColoringFilter", + "uuid": "30759600-7c02-4650-b5ca-e7036d6b568e" + }, + "isDisabled": false + }, + { + "args": { + "export_file_path": { + "value": "/Users/mjackson/Workspace3/simplnx/vv_work/face_ipf/out_nx_fixed.dream3d", + "version": 1 + }, + "write_xdmf_file": { + "value": false, + "version": 1 + } + }, + "comments": "", + "filter": { + "name": "nx::core::WriteDREAM3DFilter", + "uuid": "b3a95784-2ced-41ec-8d3d-0242ac130003" + }, + "isDisabled": false + } + ], + "version": 1 +} \ No newline at end of file From b5224cec0e051592861f442f182cc2422239f11c Mon Sep 17 00:00:00 2001 From: Michael Jackson Date: Wed, 1 Jul 2026 11:24:07 -0400 Subject: [PATCH 2/5] DOC: Clarify SIMPLNX files are not legacy-readable by design Reword the legacy-comparison notes for ComputeFaceIPFColoring: SIMPLNX-written .dream3d files are intentionally not readable by legacy DREAM3D 6.5.171 (the FileVersion dataset gates it). Feeding an NX file to the legacy PipelineRunner is not a supported path, so authoring the legacy A/B input natively with the legacy_dream3d writer is the standard approach -- not a workaround for a bug. Removes the incorrect "NX->legacy read gap / worth filing as its own issue" framing. Signed-off-by: Michael Jackson --- .../vv/ComputeFaceIPFColoringFilter.md | 2 +- vv_work/face_ipf/build_fixture.py | 51 ------ vv_work/face_ipf/fx_6_5_171.json | 170 ------------------ vv_work/face_ipf/fx_6_5_172.json | 170 ------------------ vv_work/face_ipf/gen_nx_pipeline.py | 48 ----- vv_work/face_ipf/gen_pipeline.py | 128 ------------- vv_work/face_ipf/legacy_FaceIPF_6_5_171.json | 170 ------------------ vv_work/face_ipf/legacy_FaceIPF_6_5_172.json | 170 ------------------ vv_work/face_ipf/legacy_comparison_summary.md | 82 --------- vv_work/face_ipf/nx_FaceIPF.d3dpipeline | 83 --------- 10 files changed, 1 insertion(+), 1073 deletions(-) delete mode 100644 vv_work/face_ipf/build_fixture.py delete mode 100644 vv_work/face_ipf/fx_6_5_171.json delete mode 100644 vv_work/face_ipf/fx_6_5_172.json delete mode 100644 vv_work/face_ipf/gen_nx_pipeline.py delete mode 100644 vv_work/face_ipf/gen_pipeline.py delete mode 100644 vv_work/face_ipf/legacy_FaceIPF_6_5_171.json delete mode 100644 vv_work/face_ipf/legacy_FaceIPF_6_5_172.json delete mode 100644 vv_work/face_ipf/legacy_comparison_summary.md delete mode 100644 vv_work/face_ipf/nx_FaceIPF.d3dpipeline diff --git a/src/Plugins/OrientationAnalysis/vv/ComputeFaceIPFColoringFilter.md b/src/Plugins/OrientationAnalysis/vv/ComputeFaceIPFColoringFilter.md index 2548421f73..494c00ed3b 100644 --- a/src/Plugins/OrientationAnalysis/vv/ComputeFaceIPFColoringFilter.md +++ b/src/Plugins/OrientationAnalysis/vv/ComputeFaceIPFColoringFilter.md @@ -99,4 +99,4 @@ Three-way binary comparison (6.5.171 / 6.5.172 / SIMPLNX) on a hand-built legacy - `ComputeFaceIPFColoringFilter-D1` — Phase-2 face side colored with Phase-1's Laue symmetry operator (and left black on `feature1`-invalid boundary faces). The #1635 bug; 6.5.171 reproduces it, 6.5.172 and SIMPLNX fix it. Empirically 120,000/756,474 faces affected on real data. See `vv/deviations/ComputeFaceIPFColoringFilter.md`. - `ComputeFaceIPFColoringFilter-D2` — hex basal IPF hue differs between EbsdLib 3.0.0 (SIMPLNX, green) and legacy EbsdLib (6.5.171 & 6.5.172, blue); a library deviation affecting all hex IPF coloring, flagged for review. -*Note:* the NX-written `6_6_Small_IN100_GBCD` surface mesh cannot be read by legacy `DataContainerReader` (both 6.5.171 and 6.5.172 fail identically — `DataArray` connectivity). The legacy-native fixture was authored with the `legacy_dream3d` writer to run the binary A/B. The NX→legacy geometry read gap is worth filing as its own issue. +*Note:* SIMPLNX-written `.dream3d` files are, by design, not readable by legacy DREAM3D 6.5.171 (the FileVersion dataset gates this), so the legacy A/B input was authored directly in legacy format with the `legacy_dream3d` writer — the standard approach for legacy comparisons. diff --git a/vv_work/face_ipf/build_fixture.py b/vv_work/face_ipf/build_fixture.py deleted file mode 100644 index 5d1b15792e..0000000000 --- a/vv_work/face_ipf/build_fixture.py +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env python3 -"""Build a legacy-native triangle-mesh .dream3d fixture for the FaceIPF A/B. - -Mirrors the Class 1 analytical unit-test fixture: 4 faces, feature1=cubic / feature2=hex, -identity orientations, corner-aligned face normals. Both legacy PipelineRunner binaries -(6.5.171 buggy, 6.5.172 fixed) can read it. -""" -import sys -import numpy as np - -sys.path.insert(0, "/Users/mjackson/Workspace1/Claude_Support/skills/compare-legacy-dream3d") -from legacy_dream3d import D3DLegacyWriter - -OUT = "/Users/mjackson/Workspace3/simplnx/vv_work/face_ipf/fixture_legacy.dream3d" - -# 4 faces. Trivial connectivity (3 unique verts per triangle); positions are irrelevant to -# IPF coloring (the filter reads the FaceNormals array, not computed normals). -n_faces = 4 -vertices = np.zeros((n_faces * 3, 3), dtype=np.float32) -for i in range(n_faces * 3): - vertices[i] = [float(i), 0.0, 0.0] -triangles = np.arange(n_faces * 3, dtype=np.int64).reshape(n_faces, 3) - -face_labels = np.array([[1, 2], [-1, 2], [1, -1], [1, 3]], dtype=np.int32) -face_normals = np.array([[-1.0, 0.0, 0.0], - [-1.0, 0.0, 0.0], - [-1.0, 0.0, 0.0], - [1.0, 1.0, 1.0]], dtype=np.float64) - -# 4 features (index 0 unused); identity Euler angles -avg_euler = np.zeros((4, 3), dtype=np.float32) -phases = np.array([[0], [1], [2], [1]], dtype=np.int32) -# phase 0 unknown(999), phase 1 cubic-high(1), phase 2 hex-high(0) -crystal_structures = np.array([[999], [1], [0]], dtype=np.uint32) - -with D3DLegacyWriter(OUT) as w: - # Triangle DC: geometry + face data - w.add_triangle_geom("TriangleDataContainer", vertices, triangles) - w.add_attribute_matrix("TriangleDataContainer", "FaceData", (n_faces,), "Face") - w.add_data_array("TriangleDataContainer", "FaceData", "FaceLabels", face_labels, comp_dims=(2,)) - w.add_data_array("TriangleDataContainer", "FaceData", "FaceNormals", face_normals, comp_dims=(3,)) - - # Image DC: feature + ensemble data (GenerateFaceIPFColoring requires these in an Image geom) - w.add_image_geom("Small IN100", dims=(1, 1, 1)) - w.add_attribute_matrix("Small IN100", "Grain Data", (4,), "CellFeature") - w.add_data_array("Small IN100", "Grain Data", "AvgEulerAngles", avg_euler, comp_dims=(3,)) - w.add_data_array("Small IN100", "Grain Data", "Phases", phases, comp_dims=(1,)) - w.add_attribute_matrix("Small IN100", "Phase Data", (3,), "CellEnsemble") - w.add_data_array("Small IN100", "Phase Data", "CrystalStructures", crystal_structures, comp_dims=(1,)) - -print("wrote", OUT) diff --git a/vv_work/face_ipf/fx_6_5_171.json b/vv_work/face_ipf/fx_6_5_171.json deleted file mode 100644 index 3afb2eb1ba..0000000000 --- a/vv_work/face_ipf/fx_6_5_171.json +++ /dev/null @@ -1,170 +0,0 @@ -{ - "0": { - "FilterVersion": "6.5.171", - "Filter_Enabled": true, - "Filter_Human_Label": "Read DREAM.3D Data File", - "Filter_Name": "DataContainerReader", - "Filter_Uuid": "{043cbde5-3878-5718-958f-ae75714df0df}", - "InputFile": "/Users/mjackson/Workspace3/simplnx/vv_work/face_ipf/fixture_legacy.dream3d", - "OverwriteExistingDataContainers": false, - "InputFileDataContainerArrayProxy": { - "Data Containers": [ - { - "Attribute Matricies": [ - { - "Data Arrays": [ - { - "Component Dimensions": [ - 2 - ], - "Flag": 2, - "Name": "FaceLabels", - "Object Type": "DataArray", - "Path": "/DataContainers/TriangleDataContainer/FaceData", - "Tuple Dimensions": [ - 1 - ], - "Version": 2 - }, - { - "Component Dimensions": [ - 3 - ], - "Flag": 2, - "Name": "FaceNormals", - "Object Type": "DataArray", - "Path": "/DataContainers/TriangleDataContainer/FaceData", - "Tuple Dimensions": [ - 1 - ], - "Version": 2 - } - ], - "Flag": 2, - "Name": "FaceData", - "Type": 2 - } - ], - "Flag": 2, - "Geometry": { - "Geometry_Type": 4, - "Geometry_Type_Name": "TriangleGeometry" - }, - "Name": "TriangleDataContainer" - }, - { - "Attribute Matricies": [ - { - "Data Arrays": [ - { - "Component Dimensions": [ - 3 - ], - "Flag": 2, - "Name": "AvgEulerAngles", - "Object Type": "DataArray", - "Path": "/DataContainers/Small IN100/Grain Data", - "Tuple Dimensions": [ - 1 - ], - "Version": 2 - }, - { - "Component Dimensions": [ - 1 - ], - "Flag": 2, - "Name": "Phases", - "Object Type": "DataArray", - "Path": "/DataContainers/Small IN100/Grain Data", - "Tuple Dimensions": [ - 1 - ], - "Version": 2 - } - ], - "Flag": 2, - "Name": "Grain Data", - "Type": 7 - }, - { - "Data Arrays": [ - { - "Component Dimensions": [ - 1 - ], - "Flag": 2, - "Name": "CrystalStructures", - "Object Type": "DataArray", - "Path": "/DataContainers/Small IN100/Phase Data", - "Tuple Dimensions": [ - 1 - ], - "Version": 2 - } - ], - "Flag": 2, - "Name": "Phase Data", - "Type": 11 - } - ], - "Flag": 2, - "Geometry": { - "Geometry_Type": 0, - "Geometry_Type_Name": "ImageGeometry" - }, - "Name": "Small IN100" - } - ], - "Version": 6 - } - }, - "1": { - "FilterVersion": "6.5.171", - "Filter_Enabled": true, - "Filter_Human_Label": "Generate IPF Colors (Face)", - "Filter_Name": "GenerateFaceIPFColoring", - "Filter_Uuid": "{0a121e03-3922-5c29-962d-40d88653f4b6}", - "SurfaceMeshFaceLabelsArrayPath": { - "Data Container Name": "TriangleDataContainer", - "Attribute Matrix Name": "FaceData", - "Data Array Name": "FaceLabels" - }, - "SurfaceMeshFaceNormalsArrayPath": { - "Data Container Name": "TriangleDataContainer", - "Attribute Matrix Name": "FaceData", - "Data Array Name": "FaceNormals" - }, - "FeatureEulerAnglesArrayPath": { - "Data Container Name": "Small IN100", - "Attribute Matrix Name": "Grain Data", - "Data Array Name": "AvgEulerAngles" - }, - "FeaturePhasesArrayPath": { - "Data Container Name": "Small IN100", - "Attribute Matrix Name": "Grain Data", - "Data Array Name": "Phases" - }, - "CrystalStructuresArrayPath": { - "Data Container Name": "Small IN100", - "Attribute Matrix Name": "Phase Data", - "Data Array Name": "CrystalStructures" - }, - "SurfaceMeshFaceIPFColorsArrayName": "NXCompare_IPFColors" - }, - "2": { - "FilterVersion": "6.5.171", - "Filter_Enabled": true, - "Filter_Human_Label": "Write DREAM.3D Data File", - "Filter_Name": "DataContainerWriter", - "Filter_Uuid": "{3fcd4c43-9d75-5b86-aad4-4441bc914f37}", - "OutputFile": "/Users/mjackson/Workspace3/simplnx/vv_work/face_ipf/fx_out_6_5_171.dream3d", - "WriteTimeSeries": false, - "WriteXdmfFile": false - }, - "PipelineBuilder": { - "Name": "FaceIPF legacy AB 6_5_171", - "Number_Filters": 3, - "Version": 6 - } -} \ No newline at end of file diff --git a/vv_work/face_ipf/fx_6_5_172.json b/vv_work/face_ipf/fx_6_5_172.json deleted file mode 100644 index 22fbbbcfb2..0000000000 --- a/vv_work/face_ipf/fx_6_5_172.json +++ /dev/null @@ -1,170 +0,0 @@ -{ - "0": { - "FilterVersion": "6.5.171", - "Filter_Enabled": true, - "Filter_Human_Label": "Read DREAM.3D Data File", - "Filter_Name": "DataContainerReader", - "Filter_Uuid": "{043cbde5-3878-5718-958f-ae75714df0df}", - "InputFile": "/Users/mjackson/Workspace3/simplnx/vv_work/face_ipf/fixture_legacy.dream3d", - "OverwriteExistingDataContainers": false, - "InputFileDataContainerArrayProxy": { - "Data Containers": [ - { - "Attribute Matricies": [ - { - "Data Arrays": [ - { - "Component Dimensions": [ - 2 - ], - "Flag": 2, - "Name": "FaceLabels", - "Object Type": "DataArray", - "Path": "/DataContainers/TriangleDataContainer/FaceData", - "Tuple Dimensions": [ - 1 - ], - "Version": 2 - }, - { - "Component Dimensions": [ - 3 - ], - "Flag": 2, - "Name": "FaceNormals", - "Object Type": "DataArray", - "Path": "/DataContainers/TriangleDataContainer/FaceData", - "Tuple Dimensions": [ - 1 - ], - "Version": 2 - } - ], - "Flag": 2, - "Name": "FaceData", - "Type": 2 - } - ], - "Flag": 2, - "Geometry": { - "Geometry_Type": 4, - "Geometry_Type_Name": "TriangleGeometry" - }, - "Name": "TriangleDataContainer" - }, - { - "Attribute Matricies": [ - { - "Data Arrays": [ - { - "Component Dimensions": [ - 3 - ], - "Flag": 2, - "Name": "AvgEulerAngles", - "Object Type": "DataArray", - "Path": "/DataContainers/Small IN100/Grain Data", - "Tuple Dimensions": [ - 1 - ], - "Version": 2 - }, - { - "Component Dimensions": [ - 1 - ], - "Flag": 2, - "Name": "Phases", - "Object Type": "DataArray", - "Path": "/DataContainers/Small IN100/Grain Data", - "Tuple Dimensions": [ - 1 - ], - "Version": 2 - } - ], - "Flag": 2, - "Name": "Grain Data", - "Type": 7 - }, - { - "Data Arrays": [ - { - "Component Dimensions": [ - 1 - ], - "Flag": 2, - "Name": "CrystalStructures", - "Object Type": "DataArray", - "Path": "/DataContainers/Small IN100/Phase Data", - "Tuple Dimensions": [ - 1 - ], - "Version": 2 - } - ], - "Flag": 2, - "Name": "Phase Data", - "Type": 11 - } - ], - "Flag": 2, - "Geometry": { - "Geometry_Type": 0, - "Geometry_Type_Name": "ImageGeometry" - }, - "Name": "Small IN100" - } - ], - "Version": 6 - } - }, - "1": { - "FilterVersion": "6.5.171", - "Filter_Enabled": true, - "Filter_Human_Label": "Generate IPF Colors (Face)", - "Filter_Name": "GenerateFaceIPFColoring", - "Filter_Uuid": "{0a121e03-3922-5c29-962d-40d88653f4b6}", - "SurfaceMeshFaceLabelsArrayPath": { - "Data Container Name": "TriangleDataContainer", - "Attribute Matrix Name": "FaceData", - "Data Array Name": "FaceLabels" - }, - "SurfaceMeshFaceNormalsArrayPath": { - "Data Container Name": "TriangleDataContainer", - "Attribute Matrix Name": "FaceData", - "Data Array Name": "FaceNormals" - }, - "FeatureEulerAnglesArrayPath": { - "Data Container Name": "Small IN100", - "Attribute Matrix Name": "Grain Data", - "Data Array Name": "AvgEulerAngles" - }, - "FeaturePhasesArrayPath": { - "Data Container Name": "Small IN100", - "Attribute Matrix Name": "Grain Data", - "Data Array Name": "Phases" - }, - "CrystalStructuresArrayPath": { - "Data Container Name": "Small IN100", - "Attribute Matrix Name": "Phase Data", - "Data Array Name": "CrystalStructures" - }, - "SurfaceMeshFaceIPFColorsArrayName": "NXCompare_IPFColors" - }, - "2": { - "FilterVersion": "6.5.171", - "Filter_Enabled": true, - "Filter_Human_Label": "Write DREAM.3D Data File", - "Filter_Name": "DataContainerWriter", - "Filter_Uuid": "{3fcd4c43-9d75-5b86-aad4-4441bc914f37}", - "OutputFile": "/Users/mjackson/Workspace3/simplnx/vv_work/face_ipf/fx_out_6_5_172.dream3d", - "WriteTimeSeries": false, - "WriteXdmfFile": false - }, - "PipelineBuilder": { - "Name": "FaceIPF legacy AB 6_5_172", - "Number_Filters": 3, - "Version": 6 - } -} \ No newline at end of file diff --git a/vv_work/face_ipf/gen_nx_pipeline.py b/vv_work/face_ipf/gen_nx_pipeline.py deleted file mode 100644 index 2b6f0f6568..0000000000 --- a/vv_work/face_ipf/gen_nx_pipeline.py +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env python3 -import json - -IN = "/Users/mjackson/Workspace1/DREAM3D_Data/TestFiles/6_6_Small_IN100_GBCD/6_6_Small_IN100_GBCD.dream3d" -OUT = "/Users/mjackson/Workspace3/simplnx/vv_work/face_ipf/out_nx_fixed.dream3d" - - -def arg(v): - return {"value": v, "version": 1} - - -pipeline = { - "name": "FaceIPF NX fixed", - "pipeline": [ - { - "args": {"import_data_object": arg({"file_path": IN, "data_paths": None})}, - "comments": "", - "filter": {"name": "nx::core::ReadDREAM3DFilter", "uuid": "0dbd31c7-19e0-4077-83ef-f4a6459a0e2d"}, - "isDisabled": False, - }, - { - "args": { - "surface_mesh_face_labels_array_path": arg("TriangleDataContainer/FaceData/FaceLabels"), - "surface_mesh_face_normals_array_path": arg("TriangleDataContainer/FaceData/FaceNormals"), - "feature_euler_angles_array_path": arg("Small IN100/Grain Data/AvgEulerAngles"), - "feature_phases_array_path": arg("Small IN100/Grain Data/Phases"), - "crystal_structures_array_path": arg("Small IN100/Phase Data/CrystalStructures"), - "first_face_ipf_colors_array_name": arg("NX_FaceIPF_First"), - "second_face_ipf_colors_array_name": arg("NX_FaceIPF_Second"), - "color_key_index": arg(0), - }, - "comments": "", - "filter": {"name": "nx::core::ComputeFaceIPFColoringFilter", "uuid": "30759600-7c02-4650-b5ca-e7036d6b568e"}, - "isDisabled": False, - }, - { - "args": {"export_file_path": arg(OUT), "write_xdmf_file": arg(False)}, - "comments": "", - "filter": {"name": "nx::core::WriteDREAM3DFilter", "uuid": "b3a95784-2ced-41ec-8d3d-0242ac130003"}, - "isDisabled": False, - }, - ], - "version": 1, -} - -with open("/Users/mjackson/Workspace3/simplnx/vv_work/face_ipf/nx_FaceIPF.d3dpipeline", "w") as fh: - json.dump(pipeline, fh, indent=2) -print("wrote nx_FaceIPF.d3dpipeline") diff --git a/vv_work/face_ipf/gen_pipeline.py b/vv_work/face_ipf/gen_pipeline.py deleted file mode 100644 index 66e9a80c10..0000000000 --- a/vv_work/face_ipf/gen_pipeline.py +++ /dev/null @@ -1,128 +0,0 @@ -#!/usr/bin/env python3 -"""Generate SIMPL pipelines for the GenerateFaceIPFColoring legacy A/B comparison.""" -import json -import sys - -IN = "/Users/mjackson/Workspace1/DREAM3D_Data/TestFiles/6_6_Small_IN100_GBCD/6_6_Small_IN100_GBCD.dream3d" -OUT_NAME = "NXCompare_IPFColors" # created 6-component array - - -def da(name, path, obj_type, comps): - return { - "Component Dimensions": [comps], - "Flag": 2, - "Name": name, - "Object Type": obj_type, - "Path": path, - "Tuple Dimensions": [1], - "Version": 2, - } - - -def reader(input_file): - proxy = { - "Data Containers": [ - { - "Attribute Matricies": [ - { - "Data Arrays": [ - da("FaceLabels", "/DataContainers/TriangleDataContainer/FaceData", "DataArray", 2), - da("FaceNormals", "/DataContainers/TriangleDataContainer/FaceData", "DataArray", 3), - ], - "Flag": 2, - "Name": "FaceData", - "Type": 2, # Face - } - ], - "Flag": 2, - "Geometry": {"Geometry_Type": 4, "Geometry_Type_Name": "TriangleGeometry"}, - "Name": "TriangleDataContainer", - }, - { - "Attribute Matricies": [ - { - "Data Arrays": [ - da("AvgEulerAngles", "/DataContainers/Small IN100/Grain Data", "DataArray", 3), - da("Phases", "/DataContainers/Small IN100/Grain Data", "DataArray", 1), - ], - "Flag": 2, - "Name": "Grain Data", - "Type": 7, # CellFeature - }, - { - "Data Arrays": [ - da("CrystalStructures", "/DataContainers/Small IN100/Phase Data", "DataArray", 1), - ], - "Flag": 2, - "Name": "Phase Data", - "Type": 11, # CellEnsemble - }, - ], - "Flag": 2, - "Geometry": {"Geometry_Type": 0, "Geometry_Type_Name": "ImageGeometry"}, - "Name": "Small IN100", - }, - ], - "Version": 6, - } - return { - "FilterVersion": "6.5.171", - "Filter_Enabled": True, - "Filter_Human_Label": "Read DREAM.3D Data File", - "Filter_Name": "DataContainerReader", - "Filter_Uuid": "{043cbde5-3878-5718-958f-ae75714df0df}", - "InputFile": input_file, - "OverwriteExistingDataContainers": False, - "InputFileDataContainerArrayProxy": proxy, - } - - -def dap(dc, am, name): - return {"Data Container Name": dc, "Attribute Matrix Name": am, "Data Array Name": name} - - -def ipf_filter(): - return { - "FilterVersion": "6.5.171", - "Filter_Enabled": True, - "Filter_Human_Label": "Generate IPF Colors (Face)", - "Filter_Name": "GenerateFaceIPFColoring", - "Filter_Uuid": "{0a121e03-3922-5c29-962d-40d88653f4b6}", - "SurfaceMeshFaceLabelsArrayPath": dap("TriangleDataContainer", "FaceData", "FaceLabels"), - "SurfaceMeshFaceNormalsArrayPath": dap("TriangleDataContainer", "FaceData", "FaceNormals"), - "FeatureEulerAnglesArrayPath": dap("Small IN100", "Grain Data", "AvgEulerAngles"), - "FeaturePhasesArrayPath": dap("Small IN100", "Grain Data", "Phases"), - "CrystalStructuresArrayPath": dap("Small IN100", "Phase Data", "CrystalStructures"), - "SurfaceMeshFaceIPFColorsArrayName": OUT_NAME, - } - - -def writer(output_file): - return { - "FilterVersion": "6.5.171", - "Filter_Enabled": True, - "Filter_Human_Label": "Write DREAM.3D Data File", - "Filter_Name": "DataContainerWriter", - "Filter_Uuid": "{3fcd4c43-9d75-5b86-aad4-4441bc914f37}", - "OutputFile": output_file, - "WriteTimeSeries": False, - "WriteXdmfFile": False, - } - - -def build(output_file, name): - return { - "0": reader(IN), - "1": ipf_filter(), - "2": writer(output_file), - "PipelineBuilder": {"Name": name, "Number_Filters": 3, "Version": 6}, - } - - -if __name__ == "__main__": - out_dir = "/Users/mjackson/Workspace3/simplnx/vv_work/face_ipf" - for ver in ("6_5_171", "6_5_172"): - pl = build(f"{out_dir}/out_{ver}.dream3d", f"FaceIPF legacy AB {ver}") - with open(f"{out_dir}/legacy_FaceIPF_{ver}.json", "w") as fh: - json.dump(pl, fh, indent=2) - print(f"wrote legacy_FaceIPF_{ver}.json") diff --git a/vv_work/face_ipf/legacy_FaceIPF_6_5_171.json b/vv_work/face_ipf/legacy_FaceIPF_6_5_171.json deleted file mode 100644 index f0a3793287..0000000000 --- a/vv_work/face_ipf/legacy_FaceIPF_6_5_171.json +++ /dev/null @@ -1,170 +0,0 @@ -{ - "0": { - "FilterVersion": "6.5.171", - "Filter_Enabled": true, - "Filter_Human_Label": "Read DREAM.3D Data File", - "Filter_Name": "DataContainerReader", - "Filter_Uuid": "{043cbde5-3878-5718-958f-ae75714df0df}", - "InputFile": "/Users/mjackson/Workspace3/simplnx/vv_work/face_ipf/input_legacy_readable.dream3d", - "OverwriteExistingDataContainers": false, - "InputFileDataContainerArrayProxy": { - "Data Containers": [ - { - "Attribute Matricies": [ - { - "Data Arrays": [ - { - "Component Dimensions": [ - 2 - ], - "Flag": 2, - "Name": "FaceLabels", - "Object Type": "DataArray", - "Path": "/DataContainers/TriangleDataContainer/FaceData", - "Tuple Dimensions": [ - 1 - ], - "Version": 2 - }, - { - "Component Dimensions": [ - 3 - ], - "Flag": 2, - "Name": "FaceNormals", - "Object Type": "DataArray", - "Path": "/DataContainers/TriangleDataContainer/FaceData", - "Tuple Dimensions": [ - 1 - ], - "Version": 2 - } - ], - "Flag": 2, - "Name": "FaceData", - "Type": 2 - } - ], - "Flag": 2, - "Geometry": { - "Geometry_Type": 4, - "Geometry_Type_Name": "TriangleGeometry" - }, - "Name": "TriangleDataContainer" - }, - { - "Attribute Matricies": [ - { - "Data Arrays": [ - { - "Component Dimensions": [ - 3 - ], - "Flag": 2, - "Name": "AvgEulerAngles", - "Object Type": "DataArray", - "Path": "/DataContainers/Small IN100/Grain Data", - "Tuple Dimensions": [ - 1 - ], - "Version": 2 - }, - { - "Component Dimensions": [ - 1 - ], - "Flag": 2, - "Name": "Phases", - "Object Type": "DataArray", - "Path": "/DataContainers/Small IN100/Grain Data", - "Tuple Dimensions": [ - 1 - ], - "Version": 2 - } - ], - "Flag": 2, - "Name": "Grain Data", - "Type": 7 - }, - { - "Data Arrays": [ - { - "Component Dimensions": [ - 1 - ], - "Flag": 2, - "Name": "CrystalStructures", - "Object Type": "DataArray", - "Path": "/DataContainers/Small IN100/Phase Data", - "Tuple Dimensions": [ - 1 - ], - "Version": 2 - } - ], - "Flag": 2, - "Name": "Phase Data", - "Type": 11 - } - ], - "Flag": 2, - "Geometry": { - "Geometry_Type": 0, - "Geometry_Type_Name": "ImageGeometry" - }, - "Name": "Small IN100" - } - ], - "Version": 6 - } - }, - "1": { - "FilterVersion": "6.5.171", - "Filter_Enabled": true, - "Filter_Human_Label": "Generate IPF Colors (Face)", - "Filter_Name": "GenerateFaceIPFColoring", - "Filter_Uuid": "{0a121e03-3922-5c29-962d-40d88653f4b6}", - "SurfaceMeshFaceLabelsArrayPath": { - "Data Container Name": "TriangleDataContainer", - "Attribute Matrix Name": "FaceData", - "Data Array Name": "FaceLabels" - }, - "SurfaceMeshFaceNormalsArrayPath": { - "Data Container Name": "TriangleDataContainer", - "Attribute Matrix Name": "FaceData", - "Data Array Name": "FaceNormals" - }, - "FeatureEulerAnglesArrayPath": { - "Data Container Name": "Small IN100", - "Attribute Matrix Name": "Grain Data", - "Data Array Name": "AvgEulerAngles" - }, - "FeaturePhasesArrayPath": { - "Data Container Name": "Small IN100", - "Attribute Matrix Name": "Grain Data", - "Data Array Name": "Phases" - }, - "CrystalStructuresArrayPath": { - "Data Container Name": "Small IN100", - "Attribute Matrix Name": "Phase Data", - "Data Array Name": "CrystalStructures" - }, - "SurfaceMeshFaceIPFColorsArrayName": "NXCompare_IPFColors" - }, - "2": { - "FilterVersion": "6.5.171", - "Filter_Enabled": true, - "Filter_Human_Label": "Write DREAM.3D Data File", - "Filter_Name": "DataContainerWriter", - "Filter_Uuid": "{3fcd4c43-9d75-5b86-aad4-4441bc914f37}", - "OutputFile": "/Users/mjackson/Workspace3/simplnx/vv_work/face_ipf/out_6_5_171.dream3d", - "WriteTimeSeries": false, - "WriteXdmfFile": false - }, - "PipelineBuilder": { - "Name": "FaceIPF legacy AB 6_5_171", - "Number_Filters": 3, - "Version": 6 - } -} \ No newline at end of file diff --git a/vv_work/face_ipf/legacy_FaceIPF_6_5_172.json b/vv_work/face_ipf/legacy_FaceIPF_6_5_172.json deleted file mode 100644 index df6868cfab..0000000000 --- a/vv_work/face_ipf/legacy_FaceIPF_6_5_172.json +++ /dev/null @@ -1,170 +0,0 @@ -{ - "0": { - "FilterVersion": "6.5.171", - "Filter_Enabled": true, - "Filter_Human_Label": "Read DREAM.3D Data File", - "Filter_Name": "DataContainerReader", - "Filter_Uuid": "{043cbde5-3878-5718-958f-ae75714df0df}", - "InputFile": "/Users/mjackson/Workspace3/simplnx/vv_work/face_ipf/input_legacy_readable.dream3d", - "OverwriteExistingDataContainers": false, - "InputFileDataContainerArrayProxy": { - "Data Containers": [ - { - "Attribute Matricies": [ - { - "Data Arrays": [ - { - "Component Dimensions": [ - 2 - ], - "Flag": 2, - "Name": "FaceLabels", - "Object Type": "DataArray", - "Path": "/DataContainers/TriangleDataContainer/FaceData", - "Tuple Dimensions": [ - 1 - ], - "Version": 2 - }, - { - "Component Dimensions": [ - 3 - ], - "Flag": 2, - "Name": "FaceNormals", - "Object Type": "DataArray", - "Path": "/DataContainers/TriangleDataContainer/FaceData", - "Tuple Dimensions": [ - 1 - ], - "Version": 2 - } - ], - "Flag": 2, - "Name": "FaceData", - "Type": 2 - } - ], - "Flag": 2, - "Geometry": { - "Geometry_Type": 4, - "Geometry_Type_Name": "TriangleGeometry" - }, - "Name": "TriangleDataContainer" - }, - { - "Attribute Matricies": [ - { - "Data Arrays": [ - { - "Component Dimensions": [ - 3 - ], - "Flag": 2, - "Name": "AvgEulerAngles", - "Object Type": "DataArray", - "Path": "/DataContainers/Small IN100/Grain Data", - "Tuple Dimensions": [ - 1 - ], - "Version": 2 - }, - { - "Component Dimensions": [ - 1 - ], - "Flag": 2, - "Name": "Phases", - "Object Type": "DataArray", - "Path": "/DataContainers/Small IN100/Grain Data", - "Tuple Dimensions": [ - 1 - ], - "Version": 2 - } - ], - "Flag": 2, - "Name": "Grain Data", - "Type": 7 - }, - { - "Data Arrays": [ - { - "Component Dimensions": [ - 1 - ], - "Flag": 2, - "Name": "CrystalStructures", - "Object Type": "DataArray", - "Path": "/DataContainers/Small IN100/Phase Data", - "Tuple Dimensions": [ - 1 - ], - "Version": 2 - } - ], - "Flag": 2, - "Name": "Phase Data", - "Type": 11 - } - ], - "Flag": 2, - "Geometry": { - "Geometry_Type": 0, - "Geometry_Type_Name": "ImageGeometry" - }, - "Name": "Small IN100" - } - ], - "Version": 6 - } - }, - "1": { - "FilterVersion": "6.5.171", - "Filter_Enabled": true, - "Filter_Human_Label": "Generate IPF Colors (Face)", - "Filter_Name": "GenerateFaceIPFColoring", - "Filter_Uuid": "{0a121e03-3922-5c29-962d-40d88653f4b6}", - "SurfaceMeshFaceLabelsArrayPath": { - "Data Container Name": "TriangleDataContainer", - "Attribute Matrix Name": "FaceData", - "Data Array Name": "FaceLabels" - }, - "SurfaceMeshFaceNormalsArrayPath": { - "Data Container Name": "TriangleDataContainer", - "Attribute Matrix Name": "FaceData", - "Data Array Name": "FaceNormals" - }, - "FeatureEulerAnglesArrayPath": { - "Data Container Name": "Small IN100", - "Attribute Matrix Name": "Grain Data", - "Data Array Name": "AvgEulerAngles" - }, - "FeaturePhasesArrayPath": { - "Data Container Name": "Small IN100", - "Attribute Matrix Name": "Grain Data", - "Data Array Name": "Phases" - }, - "CrystalStructuresArrayPath": { - "Data Container Name": "Small IN100", - "Attribute Matrix Name": "Phase Data", - "Data Array Name": "CrystalStructures" - }, - "SurfaceMeshFaceIPFColorsArrayName": "NXCompare_IPFColors" - }, - "2": { - "FilterVersion": "6.5.171", - "Filter_Enabled": true, - "Filter_Human_Label": "Write DREAM.3D Data File", - "Filter_Name": "DataContainerWriter", - "Filter_Uuid": "{3fcd4c43-9d75-5b86-aad4-4441bc914f37}", - "OutputFile": "/Users/mjackson/Workspace3/simplnx/vv_work/face_ipf/out_6_5_172.dream3d", - "WriteTimeSeries": false, - "WriteXdmfFile": false - }, - "PipelineBuilder": { - "Name": "FaceIPF legacy AB 6_5_172", - "Number_Filters": 3, - "Version": 6 - } -} \ No newline at end of file diff --git a/vv_work/face_ipf/legacy_comparison_summary.md b/vv_work/face_ipf/legacy_comparison_summary.md deleted file mode 100644 index 69f395face..0000000000 --- a/vv_work/face_ipf/legacy_comparison_summary.md +++ /dev/null @@ -1,82 +0,0 @@ -# Legacy Comparison: ComputeFaceIPFColoring (issue #1635) - -Date: 2026-06-30 - -## Goal - -Three-way A/B of the Phase-2 face-IPF coloring: SIMPLNX (fixed) vs DREAM3D 6.5.171 (buggy) vs DREAM3D 6.5.172 (fixed). - -## Environment - -- **6.5.171 (buggy):** `/Users/mjackson/Applications/DREAM3D.app/Contents/bin/PipelineRunner` (Apr 2023) -- **6.5.172 (fixed):** `/Users/mjackson/Workspace3/6.5.172/DREAM3D-Build/D3D-Rel-Qt515-6_5_171/Bin/PipelineRunner` -- **SIMPLNX (fixed):** `…/DREAM3D-Build/NX-Com-Qt69-Vtk96-Rel/Bin/nxrunner` - -## Inputs - -1. **`fixture_legacy.dream3d`** — a legacy-native 4-face triangle mesh (built with the - `legacy_dream3d` writer extended with `add_triangle_geom`), mirroring the Class 1 analytical - unit-test fixture: feature1 = cubic (phase 1), feature2 = hex (phase 2), identity orientations, - corner-aligned face normals. Both legacy `PipelineRunner` binaries read it without error. - - F0 `(1,2)` cubic|hex (mixed-phase), F1 `(-1,2)` --|hex (boundary), F2 `(1,-1)` cubic|--, F3 `(1,3)` cubic|cubic -2. **`6_6_Small_IN100_GBCD.dream3d`** — the real 756,474-face mesh, used for the SIMPLNX-vs-baked - real-data check (legacy binaries cannot read its NX-written geometry; see Note A). - -## Result 1 — three-way on the legacy-native fixture (the headline) - -Second (Phase-2 side) color per face. Cubic first-side colors and the cubic|cubic face (F3) agree across all three. - -| Face | kind | 6.5.171 (buggy) | 6.5.172 (fixed) | SIMPLNX (fixed) | -|---|---|---|---|---| -| F0 | cubic\|hex (mixed) | **(255,0,0)** wrong-phase cubic | (0,0,255) hex | (0,255,0) hex | -| F1 | --\|hex (boundary) | **(0,0,0)** black | (0,0,255) hex | (0,255,0) hex | -| F2 | cubic\|-- | (0,0,0) black | (0,0,0) black | (0,0,0) black | -| F3 | cubic\|cubic | (0,0,255) | (0,0,255) | (0,0,255) | - -Two independent deviations fall out cleanly: - -- **D1 — the #1635 bug (root cause: bug in 6.5.171).** On F0 and F1, 6.5.171 colors the Phase-2 - side with Phase-1's symmetry (F0 → cubic red instead of a hex color) or leaves it black - (F1, `feature1 ≤ 0`). 6.5.172 **and** SIMPLNX both apply the hex operator. 6.5.171's output - matches the hand-derived *buggy* oracle exactly. **The fix is confirmed on real legacy binaries.** -- **D2 — EbsdLib hex basal hue (root cause: library).** Where both fixed implementations apply - the hex operator (F0, F1), legacy EbsdLib (6.5.172) produces **blue (0,0,255)** and EbsdLib - 3.0.0 (SIMPLNX) produces **green (0,255,0)** for the same hex basal direction. Cubic coloring - is identical across all three. This is independent of #1635 — it is the hex IPF basal-corner - hue assignment changing between EbsdLib versions. Both colors have red-channel 0 (a basal - direction is never the red c-axis corner), so the convention-independent invariant holds for - both; only the green/blue hue assignment differs. - -## Result 2 — SIMPLNX (fixed) vs baked buggy baseline on the real 756,474-face mesh - -The baked `SurfaceMeshFaceIPFColors` in `6_6_Small_IN100_GBCD.dream3d` is authentic buggy -(6.5.171-class) output. SIMPLNX-fixed vs that baseline differs on **exactly 120,000 faces**, all -of them `feature1 ≤ 0` boundary faces (the D1 boundary manifestation), **0** elsewhere, **0** on -the 636,474 interior faces. (Single-phase data, so no mixed-phase or hex faces appear — D2 does -not show here.) - -## Note A — NX→legacy geometry incompatibility (separate finding) - -The NX-written `6_6_Small_IN100_GBCD.dream3d` cannot be read by either legacy `PipelineRunner` -(both 6.5.171 and 6.5.172 fail identically: `SharedTriList` loads as 0 tuples → `-10200`). Root -cause: DREAM3D-NX writes the triangle connectivity as `DataArray`, which legacy -`H5DataArrayReader` does not recognize (legacy `SharedTriList = DataArray`); the legacy -reader does not reconstruct the NX `_SIMPL_GEOMETRY` group. This is why the fixture in Input 1 was -authored natively in legacy format. Worth filing as its own NX-export issue; unrelated to the IPF filter. - -## Conclusion - -- **D1 (#1635):** confirmed and fixed. Trust SIMPLNX / 6.5.172. 6.5.171 was wrong. -- **D2 (hex hue):** library deviation between EbsdLib (green) and legacy EbsdLib (blue); affects - all hex IPF coloring (face and cell). **Resolved:** EbsdLib is the canonical IPF authority and - EbsdLib 3.0.0 and 3.1.0 agree on green (the Class 1 oracle test passes unchanged on a from-source - EbsdLib 3.1.0 build, `NX-Com-Qt69-Vtk95-Rel-EbsdLib`); legacy DREAM3D's blue is the deviation. - Trust SIMPLNX; expect hex IPF hues to change vs. legacy. -- The Class 1 oracle's exact hex value (green) is the canonical EbsdLib value (3.0.0 == 3.1.0); the - convention-independent assertion is red-channel == 0, which every version satisfies. - -## Fixes Applied - -- SIMPLNX `Algorithms/ComputeFaceIPFColoring.cpp:114,123` `phase1`→`phase2`; `KNOWN BUG` block - removed. No legacy code modified (6.5.172 already carried the D1 fix; D2 is not fixed here). -- `legacy_dream3d` writer extended with `add_triangle_geom` (Gate-1 validated). diff --git a/vv_work/face_ipf/nx_FaceIPF.d3dpipeline b/vv_work/face_ipf/nx_FaceIPF.d3dpipeline deleted file mode 100644 index 512f620c3e..0000000000 --- a/vv_work/face_ipf/nx_FaceIPF.d3dpipeline +++ /dev/null @@ -1,83 +0,0 @@ -{ - "name": "FaceIPF NX fixed", - "pipeline": [ - { - "args": { - "import_data_object": { - "value": { - "file_path": "/Users/mjackson/Workspace1/DREAM3D_Data/TestFiles/6_6_Small_IN100_GBCD/6_6_Small_IN100_GBCD.dream3d", - "data_paths": null - }, - "version": 1 - } - }, - "comments": "", - "filter": { - "name": "nx::core::ReadDREAM3DFilter", - "uuid": "0dbd31c7-19e0-4077-83ef-f4a6459a0e2d" - }, - "isDisabled": false - }, - { - "args": { - "surface_mesh_face_labels_array_path": { - "value": "TriangleDataContainer/FaceData/FaceLabels", - "version": 1 - }, - "surface_mesh_face_normals_array_path": { - "value": "TriangleDataContainer/FaceData/FaceNormals", - "version": 1 - }, - "feature_euler_angles_array_path": { - "value": "Small IN100/Grain Data/AvgEulerAngles", - "version": 1 - }, - "feature_phases_array_path": { - "value": "Small IN100/Grain Data/Phases", - "version": 1 - }, - "crystal_structures_array_path": { - "value": "Small IN100/Phase Data/CrystalStructures", - "version": 1 - }, - "first_face_ipf_colors_array_name": { - "value": "NX_FaceIPF_First", - "version": 1 - }, - "second_face_ipf_colors_array_name": { - "value": "NX_FaceIPF_Second", - "version": 1 - }, - "color_key_index": { - "value": 0, - "version": 1 - } - }, - "comments": "", - "filter": { - "name": "nx::core::ComputeFaceIPFColoringFilter", - "uuid": "30759600-7c02-4650-b5ca-e7036d6b568e" - }, - "isDisabled": false - }, - { - "args": { - "export_file_path": { - "value": "/Users/mjackson/Workspace3/simplnx/vv_work/face_ipf/out_nx_fixed.dream3d", - "version": 1 - }, - "write_xdmf_file": { - "value": false, - "version": 1 - } - }, - "comments": "", - "filter": { - "name": "nx::core::WriteDREAM3DFilter", - "uuid": "b3a95784-2ced-41ec-8d3d-0242ac130003" - }, - "isDisabled": false - } - ], - "version": 1 -} \ No newline at end of file From 62ab8dab809c59fc89d2cabd59671a3b4b59a010 Mon Sep 17 00:00:00 2001 From: Michael Jackson Date: Wed, 8 Jul 2026 13:54:26 -0400 Subject: [PATCH 3/5] REV: ComputeFaceIPFColoring adversarial review fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Drop the ebsdlib >=3.1.0 vcpkg bump (and registry baseline change) — this PR's tests pass identically on 3.0.0, and the WritePoleFigure API migration required by 3.1.0 lives only in #1647. Removing the bump decouples the two PRs and removes the broken-develop merge-order hazard. * Disable parallelization in the ComputeFaceIPFColoring algorithm: the worker writes UInt8Array outputs via operator[] from TBB threads, which the project thread-safety policy forbids. Same disposition as the ComputeFeatureFaceMisorientations V&V; report delta 4 updated to match instead of asserting per-face independent writes are safe. * Strengthen the Class 1 oracle against Euler-index defects: every feature now carries a distinct orientation chosen so corner colors stay hand-derivable (cubic 90 degrees about Z maps <100>/<111> onto themselves; hex 60 degrees about c is a 6/mmm symmetry op). The previous all-identity fixture could not detect a wrong-Euler-index bug of the same shape as #1635. Verified by mutation: reading feature1's Eulers on the Phase-2 side now fails the test. * Add face 4 (hex first side, normal (0,0,1)) so the stated hex c-axis red corner is actually exercised — all four corner primaries of the documented oracle are now asserted. * Report honesty fixes: coverage recounted to 10 of 13 with the -2431 path marked unreachable dead code; the vv_work/face_ipf evidence flagged as an explicit open archival action; provenance and deviations now agree on the retired exemplar's origin (legacy- generated array that byte-encodes the same ported bug). Signed-off-by: Michael Jackson --- .../Algorithms/ComputeFaceIPFColoring.cpp | 4 ++ .../test/ComputeFaceIPFColoringTest.cpp | 64 +++++++++++++------ .../vv/ComputeFaceIPFColoringFilter.md | 20 +++--- ...teFaceIPFColoringFilter-circular-oracle.md | 2 +- vcpkg-configuration.json | 2 +- vcpkg.json | 2 +- 6 files changed, 62 insertions(+), 32 deletions(-) diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeFaceIPFColoring.cpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeFaceIPFColoring.cpp index 851468065c..a97da53861 100644 --- a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeFaceIPFColoring.cpp +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeFaceIPFColoring.cpp @@ -180,6 +180,10 @@ Result<> ComputeFaceIPFColoring::operator()() ParallelDataAlgorithm parallelTask; parallelTask.setRange(0, numTriangles); parallelTask.requireArraysInMemory(algArrays); + // Per the project thread-safety policy, DataArray/DataStore access is not thread-safe even when each + // thread writes distinct indices, so parallelization stays disabled (same disposition as the + // ComputeFeatureFaceMisorientations V&V). + parallelTask.setParallelizationEnabled(false); parallelTask.execute(CalculateFaceIPFColorsImpl(faceLabels, phases, faceNormals, eulerAngles, crystalStructures, firstIpfColors, secondIpfColors, m_InputValues->ColorKey)); return {}; diff --git a/src/Plugins/OrientationAnalysis/test/ComputeFaceIPFColoringTest.cpp b/src/Plugins/OrientationAnalysis/test/ComputeFaceIPFColoringTest.cpp index 5e67e7c4b5..fa7e0784bc 100644 --- a/src/Plugins/OrientationAnalysis/test/ComputeFaceIPFColoringTest.cpp +++ b/src/Plugins/OrientationAnalysis/test/ComputeFaceIPFColoringTest.cpp @@ -1,6 +1,7 @@ #include "OrientationAnalysis/Filters/ComputeFaceIPFColoringFilter.hpp" #include "OrientationAnalysis/OrientationAnalysis_test_dirs.hpp" +#include "simplnx/Common/Constants.hpp" #include "simplnx/Core/Application.hpp" #include "simplnx/DataStructure/DataArray.hpp" #include "simplnx/DataStructure/DataGroup.hpp" @@ -63,22 +64,42 @@ DataPath faceAreas = faceDataGroup.createChildPath(nx::core::Constants::k_FaceAr // The crystal-structure enum values that index ops[] are Hexagonal_High = 0, // Cubic_High = 1 (EbsdLib/Core/EbsdLibConstants.h). // -// The fixture (identity orientations throughout) builds four faces: +// Every feature carries a DISTINCT orientation so a wrong-Euler-index bug (the +// Euler analogue of the #1635 wrong-phase-index bug) changes the output. The +// orientations are chosen so the expected corner colors remain hand-derivable: +// Feature 1 (cubic): Euler (pi/2, 0, 0) -- a 90-degree rotation about Z. It maps +// <100>-family reference directions to other <100> directions and <111> to +// <111>, so the cubic corner colors are IDENTICAL to the identity's. The +// derivation is insensitive to the active/passive convention (both give a +// <100>/<111> family member). +// Feature 2 (hex): Euler (pi/3, 0, 0) -- a 60-degree rotation about c, which +// is a 6/mmm symmetry operation, so every IPF color is IDENTICAL to the +// identity's (again convention-insensitive). +// Feature 3 (cubic): identity (0, 0, 0), distinct from feature 1. +// If the algorithm ever reads the wrong feature's Euler angles (e.g. feature1's +// on the phase-2 side), the hex side sees a 90-degree Z rotation instead of its +// own 60-degree one: reference (1,0,0) then lands ~30 degrees away in eta -- the +// opposite corner of the hex wedge -- and the exact-value assertions fail. +// +// The fixture builds five faces: // Face 0 labels (1,2) n=(-1,0,0) mixed cubic/hex -- the headline discriminator // Face 1 labels (-1,2) n=(-1,0,0) feature1 invalid -- boundary discriminator // Face 2 labels (1,-1) n=(-1,0,0) feature2 invalid -- control (second black) // Face 3 labels (1,3) n=(1,1,1) same-phase cubic -- control (bug cannot manifest) +// Face 4 labels (2,1) n=(0,0,1) hex first side -- hex c-axis corner (red) // // Why each side's expected color is what it is (FIX = correct behavior): -// F0 first = cubic(+(-1,0,0)) = <100> red = (255, 0, 0) -// F0 second = hex (-(-1,0,0)=(1,0,0)) basal = ( 0,255, 0) [BUG -> cubic <100> = (255,0,0)] -// F1 first = black (feature1 = -1 -> phase1 = 0) = ( 0, 0, 0) -// F1 second = hex (1,0,0) basal = ( 0,255, 0) [BUG -> guard reads CrystalStructures[0] -// sentinel, fails, leaves black (0,0,0)] -// F2 first = cubic(-1,0,0) = <100> red = (255, 0, 0) -// F2 second = black (feature2 = -1 -> phase2 = 0) = ( 0, 0, 0) -// F3 first = cubic(1,1,1) = <111> blue = ( 0, 0,255) -// F3 second = cubic(-(1,1,1)) = <111> blue = ( 0, 0,255) [phase1==phase2, bug cannot manifest] +// F0 first = cubic Rz(90)(-1,0,0) = <100> red = (255, 0, 0) +// F0 second = hex Rz(60)(-(-1,0,0)=(1,0,0)) basal = ( 0,255, 0) [BUG -> cubic ops = red] +// F1 first = black (feature1 = -1 -> phase1 = 0) = ( 0, 0, 0) +// F1 second = hex Rz(60)(1,0,0) basal = ( 0,255, 0) [BUG -> guard reads CrystalStructures[0] +// sentinel, fails, leaves black (0,0,0)] +// F2 first = cubic Rz(90)(-1,0,0) = <100> red = (255, 0, 0) +// F2 second = black (feature2 = -1 -> phase2 = 0) = ( 0, 0, 0) +// F3 first = cubic Rz(90)(1,1,1) = <111> blue = ( 0, 0,255) +// F3 second = cubic identity(-(1,1,1)) = <111> blue = ( 0, 0,255) [phase1==phase2, bug cannot manifest] +// F4 first = hex Rz(60)(0,0,1) = c-axis = (255, 0, 0) [chi = 0 -> pure red corner] +// F4 second = cubic Rz(90)(-(0,0,1)) = <100> red = (255, 0, 0) // // Faces 0 and 1 are the discriminators: under the pre-fix bug both their SECOND // colors are wrong (F0 -> red, F1 -> black); after the fix they are green. @@ -100,19 +121,22 @@ TEST_CASE("OrientationAnalysis::ComputeFaceIPFColoringFilter: Class 1 Oracle - m const DataPath phasesPath({"Data", "FeaturePhases"}); const DataPath crystalStructuresPath({"Data", "CrystalStructures"}); - // 4 faces, 2-component (feature1, feature2) - auto* faceLabelsArray = UnitTest::CreateTestDataArray(dataStructure, "FaceLabels", {4}, {2}, topGroup->getId()); - const std::vector faceLabelValues = {1, 2, -1, 2, 1, -1, 1, 3}; + // 5 faces, 2-component (feature1, feature2) + auto* faceLabelsArray = UnitTest::CreateTestDataArray(dataStructure, "FaceLabels", {5}, {2}, topGroup->getId()); + const std::vector faceLabelValues = {1, 2, -1, 2, 1, -1, 1, 3, 2, 1}; std::copy(faceLabelValues.begin(), faceLabelValues.end(), faceLabelsArray->begin()); - // 4 faces, 3-component face normals (not normalized; EbsdLib normalizes internally) - auto* faceNormalsArray = UnitTest::CreateTestDataArray(dataStructure, "FaceNormals", {4}, {3}, topGroup->getId()); - const std::vector faceNormalValues = {-1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, 1.0, 1.0, 1.0}; + // 5 faces, 3-component face normals (not normalized; EbsdLib normalizes internally) + auto* faceNormalsArray = UnitTest::CreateTestDataArray(dataStructure, "FaceNormals", {5}, {3}, topGroup->getId()); + const std::vector faceNormalValues = {-1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0}; std::copy(faceNormalValues.begin(), faceNormalValues.end(), faceNormalsArray->begin()); - // 4 features (index 0 unused), identity Euler angles throughout + // 4 features (index 0 unused). Distinct orientations per feature (see the derivation above): + // feature 1 (cubic): (pi/2, 0, 0) feature 2 (hex): (pi/3, 0, 0) feature 3 (cubic): identity auto* eulerAngles = UnitTest::CreateTestDataArray(dataStructure, "FeatureEulerAngles", {4}, {3}, topGroup->getId()); std::fill(eulerAngles->begin(), eulerAngles->end(), 0.0F); + (*eulerAngles)[3 * 1 + 0] = nx::core::Constants::k_PiOver2F; // feature 1: phi1 = 90 degrees + (*eulerAngles)[3 * 2 + 0] = nx::core::Constants::k_PiOver3F; // feature 2: phi1 = 60 degrees (hex symmetry op) // Feature phases: feature 1 -> phase 1 (cubic), feature 2 -> phase 2 (hex), feature 3 -> phase 1 (cubic) auto* phases = UnitTest::CreateTestDataArray(dataStructure, "FeaturePhases", {4}, {1}, topGroup->getId()); @@ -145,10 +169,10 @@ TEST_CASE("OrientationAnalysis::ComputeFaceIPFColoringFilter: Class 1 Oracle - m const auto& second = dataStructure.getDataRefAs(faceLabelsPath.replaceName(::k_SecondNXFaceIPFColors)); // Expected colors (the FIX = correct behavior). Each row is one face. - const std::array, 4> expectedFirst = {{{255, 0, 0}, {0, 0, 0}, {255, 0, 0}, {0, 0, 255}}}; - const std::array, 4> expectedSecond = {{{0, 255, 0}, {0, 255, 0}, {0, 0, 0}, {0, 0, 255}}}; + const std::array, 5> expectedFirst = {{{255, 0, 0}, {0, 0, 0}, {255, 0, 0}, {0, 0, 255}, {255, 0, 0}}}; + const std::array, 5> expectedSecond = {{{0, 255, 0}, {0, 255, 0}, {0, 0, 0}, {0, 0, 255}, {255, 0, 0}}}; - for(usize face = 0; face < 4; face++) + for(usize face = 0; face < 5; face++) { INFO(fmt::format("face {}", face)); REQUIRE(first[face * 3 + 0] == expectedFirst[face][0]); diff --git a/src/Plugins/OrientationAnalysis/vv/ComputeFaceIPFColoringFilter.md b/src/Plugins/OrientationAnalysis/vv/ComputeFaceIPFColoringFilter.md index 494c00ed3b..c4cf0b20f4 100644 --- a/src/Plugins/OrientationAnalysis/vv/ComputeFaceIPFColoringFilter.md +++ b/src/Plugins/OrientationAnalysis/vv/ComputeFaceIPFColoringFilter.md @@ -15,7 +15,7 @@ |------------------------|------------------------------------------------------------------------------------------------------------------------------| | Algorithm Relationship | **Port** of legacy `GenerateFaceIPFColoring` — per-face IPF color math is line-for-line identical. Deltas: output split from one 6-component array into two 3-component arrays, an added Color Key choice (TSL/PUCM/Nolze-Hielscher), and the EbsdLib 3.0.0 API. The 2023 wrong-phase bug (issue #1635) was ported verbatim and is **fixed here**. | | Oracle (confirmed) | **Class 1 (Analytical)** + Class 4 companion — closed-form IPF corner colors (pure primaries) on a hand-built 4-face mixed cubic/hex mesh. Encoded as `Class 1 Oracle - mixed-phase analytical` (24 color-byte assertions), all pass; verified to **fail** when the bug is reintroduced. | -| Code paths enumerated | 11 of 13 exercised; the 2 uncovered are low-value guard branches (a feature's phase has an out-of-range crystal-structure index) noted below. | +| Code paths enumerated | 10 of 13 exercised; 1 is unreachable dead code (`-2431`) and 2 are defensive guards (out-of-range crystal-structure index, out-of-range color key) noted below. | | Tests today | 4 test cases — 1 Class-1 analytical (new-for-V&V), 1 negative (preflight tuple-mismatch, 2 sections), 1 Color-Key plumbing run on real Small IN100 GBCD data, 1 SIMPL backward-compat. | | Exemplar archive | **None — analytical fixtures inlined.** The prior `Valid filter execution` test compared against a `SurfaceMeshFaceIPFColors` array baked into the shared `6_6_Small_IN100_GBCD.tar.gz`; that comparison was a circular oracle (filter's own pre-fix output) and is **retired**. | | Legacy comparison | **Three-way binary run** (6.5.171 vs 6.5.172 vs SIMPLNX) on a hand-built legacy-native cubic/hex mesh + SIMPLNX-vs-baked on the 756,474-face real mesh. **2 deviations:** D1 (the #1635 bug — 6.5.171 wrong, fixed in both 6.5.172 and SIMPLNX) and D2 (EbsdLib hex basal hue: SIMPLNX green vs legacy blue). Cubic coloring identical across all three. | @@ -37,7 +37,7 @@ 1. **Output layout** — legacy wrote a single 6-component `SurfaceMeshFaceIPFColors` array (`m_Colors[6*i+0..5]`); SIMPLNX writes two separate 3-component arrays (`FirstFaceIPFColors`, `SecondFaceIPFColors`). Pure repackaging — the six bytes per face are identical, only regrouped. No value change. 2. **Color Key parameter** — SIMPLNX adds a TSL/PUCM/Nolze-Hielscher choice routed into `generateIPFColor(..., m_ColorKey)`; legacy always used TSL. Default (TSL) reproduces legacy output exactly; the new option is additive. 3. **EbsdLib API** — `Ebsd::` → `ebsdlib::`, and `generateIPFColor` gained the `ColorKeyKind` argument. EbsdLib upgraded to 3.0.0. No change to the TSL color math at the standard-triangle corners used by the oracle. -4. **Parallelization** — both use TBB-style parallel-for over faces; SIMPLNX uses `ParallelDataAlgorithm` with `requireArraysInMemory`. No output impact (per-face independent writes). +4. **Parallelization** — legacy used TBB-style parallel-for over faces. SIMPLNX structures the loop with `ParallelDataAlgorithm` but **parallelization is disabled**: the worker writes `UInt8Array` outputs via `operator[]`, and per project thread-safety policy DataArray/DataStore access is not thread-safe even for distinct indices (same disposition as the `ComputeFeatureFaceMisorientations` V&V). No output impact. 5. **Wrong-phase bug** — the Phase-2 branch's `m_CrystalStructures[phase1]` guard and operator lookup were ported verbatim from the 2023 legacy code. **Fixed here** (`phase1`→`phase2`), matching the legacy 6.5.172 backport `1c96b3b8e`. *Material PRs since baseline:* #1631 (EbsdLib 3.0.0 + V&V cohort) added the Color Key option and first documented this bug with a `// KNOWN BUG` block; this V&V cycle removes that block and applies the fix. @@ -46,9 +46,9 @@ *Class:* **1 (Analytical)**, with a **Class 4 (Invariant)** companion. -*Applied:* IPF color is a closed-form function of (orientation, reference direction, Laue symmetry). At the standard stereographic-triangle corners the color is a pure primary, independent of implementation: cubic-high `<100>`→(255,0,0), cubic-high `<111>`→(0,0,255), hex-high c-axis→(255,0,0), hex-high basal→(0,255,0) (red channel exactly 0, since a basal direction sits at χ = χ_max so r = 1 − χ/χ_max = 0). A 4-face hand-built mesh with identity orientations and corner-aligned normals (Phase 1 = cubic, Phase 2 = hex) yields fully hand-derivable expected colors for both sides of every face. The Class 4 companion asserts the crispest bug signature: the hex (Phase-2) side of a basal face has red channel == 0, whereas the bug's cubic `<100>` lookup gives 255. +*Applied:* IPF color is a closed-form function of (orientation, reference direction, Laue symmetry). At the standard stereographic-triangle corners the color is a pure primary, independent of implementation: cubic-high `<100>`→(255,0,0), cubic-high `<111>`→(0,0,255), hex-high c-axis→(255,0,0), hex-high basal→(0,255,0) (red channel exactly 0, since a basal direction sits at χ = χ_max so r = 1 − χ/χ_max = 0). A 5-face hand-built mesh with corner-aligned normals (Phase 1 = cubic, Phase 2 = hex) yields fully hand-derivable expected colors for both sides of every face, and **all four** corner primaries are exercised (face 4 pins the hex c-axis red corner). Every feature carries a **distinct orientation** chosen so the corner colors remain hand-derivable (cubic: 90° about Z maps `<100>`→`<100>`, `<111>`→`<111>`; hex: 60° about c is a 6/mmm symmetry operation) — so a wrong-Euler-index defect (the Euler analogue of the #1635 phase-index bug) changes the output and fails the exact-value assertions. The Class 4 companion asserts the crispest bug signature: the hex (Phase-2) side of a basal face has red channel == 0, whereas the bug's cubic `<100>` lookup gives 255. -*Encoded:* `test/ComputeFaceIPFColoringTest.cpp::"OrientationAnalysis::ComputeFaceIPFColoringFilter: Class 1 Oracle - mixed-phase analytical"` — 4 faces × (3 first + 3 second) = 24 color-byte assertions + 1 invariant assertion, all pass. Verified to **fail** on Face 0's second-color red channel when the `phase1`→`phase2` fix is reverted. +*Encoded:* `test/ComputeFaceIPFColoringTest.cpp::"OrientationAnalysis::ComputeFaceIPFColoringFilter: Class 1 Oracle - mixed-phase analytical"` — 5 faces × (3 first + 3 second) = 30 color-byte assertions + 1 invariant assertion, all pass. Verified to **fail** (a) on Face 0's second-color red channel when the `phase1`→`phase2` fix is reverted, and (b) on the hex-side exact values when the Phase-2 branch is mutated to read feature1's Euler angles (wrong-Euler-index mutation check, run 2026-07-08). *Caveat (surfaced by the legacy A/B):* the exact hex basal value `(0,255,0)` green is the **EbsdLib** assignment (canonical); legacy DREAM3D's older EbsdLib assigns the other basal corner (blue) to the same direction (deviation `-D2`). The convention-independent part of the oracle is the **red channel == 0** invariant (a basal direction is never the red c-axis corner), which both EbsdLib generations satisfy and which distinguishes the fixed hex result from the bug's cubic `<100>` red. The exact green is confirmed under **both EbsdLib 3.0.0 and 3.1.0** (the test passes unchanged on a from-source 3.1.0 build), so it is the canonical value, not a single-version artifact. @@ -56,7 +56,7 @@ ## Code path coverage -11 of 13 paths exercised. +10 of 13 paths exercised (1 unreachable dead guard, 2 defensive guards untested). Source: `src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeFaceIPFColoring.cpp` (191 lines), plus `ComputeFaceIPFColoringFilter.cpp` preflight/execute. Logical phases: (a) preflight validation, (b) execute color-key routing, (c) per-face label→phase resolution, (d) Phase-1 first-color, (e) Phase-2 second-color. @@ -64,13 +64,13 @@ Source: `src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorit |----|------------------|-----------------------------------------------------------------------------------|------------------------------------------------------------------------| | 1 | (a) Preflight | face label/normal tuple mismatch → error `-2430` | `Invalid filter execution` — "Inconsistent face data tuple dimensions" | | 2 | (a) Preflight | feature euler/phase tuple mismatch → error `-2432` | `Invalid filter execution` — "Inconsistent cell data tuple dimensions" | -| 3 | (a) Preflight | face labels array missing → error `-2431` | *Not directly tested. Guarded by `ArraySelectionParameter`; low-value.* | +| 3 | (a) Preflight | face labels array missing → error `-2431` | *Unreachable dead code: the path is an `ArraySelectionParameter`, which guarantees existence before `preflightImpl` runs, and the `-2430` tuple validation would fail first anyway.* | | 4 | (b) Execute | Color Key 0/1/2 → TSL/PUCM/Nolze-Hielscher | `ColorKey choice reaches algorithm` — all three kinds | | 5 | (b) Execute | Color Key out of [0,2] → error `-24340` | *Not directly tested. `ChoicesParameter` constrains the index; low-value.* | | 6 | (c) Per-face | `feature1 > 0` → `phase1 = m_Phases[feature1]` | `Class 1 Oracle` — faces 0, 2, 3 | | 7 | (c) Per-face | `feature1 <= 0` → `phase1 = 0` | `Class 1 Oracle` — face 1 (label −1) | | 8 | (c) Per-face | `feature2 > 0` / `feature2 <= 0` → `phase2` set or 0 | `Class 1 Oracle` — faces 0/1 (valid), face 2 (−1) | -| 9 | (d) First color | `phase1 > 0` and crystal structure valid → cubic IPF color | `Class 1 Oracle` — faces 0, 2 (red), face 3 (blue) | +| 9 | (d) First color | `phase1 > 0` and crystal structure valid → own-phase IPF color | `Class 1 Oracle` — faces 0, 2 (cubic red), face 3 (cubic blue), face 4 (hex c-axis red) | | 10 | (d) First color | `phase1 <= 0` → first color black | `Class 1 Oracle` — face 1 → (0,0,0) | | 11 | (e) Second color | `phase2 > 0` and `CrystalStructures[phase2]` valid → **own-phase** IPF color (fix) | `Class 1 Oracle` — face 0 hex green, face 1 hex green, face 3 blue | | 12 | (e) Second color | `phase2 <= 0` → second color black | `Class 1 Oracle` — face 2 → (0,0,0) | @@ -80,7 +80,7 @@ Source: `src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorit | Test case | Status | Notes | |-----------|--------|-------| -| `Class 1 Oracle - mixed-phase analytical` | new-for-V&V | Hand-built 4-face cubic/hex mesh; 24 color-byte + 1 invariant assertions; analytical corner-primary oracle. Verified to fail when the bug is reintroduced. | +| `Class 1 Oracle - mixed-phase analytical` | new-for-V&V | Hand-built 5-face cubic/hex mesh with distinct per-feature orientations; 30 color-byte + 1 invariant assertions; analytical corner-primary oracle covering all four corners. Verified to fail under both the phase-index and Euler-index mutations. | | `Valid filter execution` | retired | Compared against `SurfaceMeshFaceIPFColors` baked into `6_6_Small_IN100_GBCD` — the filter's own pre-fix output (circular oracle). It encoded the bug on `feature1`-invalid boundary faces, so the fix correctly broke it. Superseded by the Class 1 analytical test; real-data exercise retained by the Color-Key test below. | | `Invalid filter execution` | kept | Two preflight tuple-mismatch sections (`-2430`, `-2432`). | | `ColorKey choice reaches algorithm` | kept | Runs the filter on the full Small IN100 GBCD surface mesh three times (TSL/PUCM/NH) and asserts the outputs differ — real-data smoke test independent of any baked exemplar. | @@ -94,7 +94,9 @@ Source: `src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorit ## Deviations from DREAM3D 6.5.171 -Three-way binary comparison (6.5.171 / 6.5.172 / SIMPLNX) on a hand-built legacy-native cubic/hex mesh, plus SIMPLNX-vs-baked on the 756,474-face real mesh. Full write-up: `vv_work/face_ipf/legacy_comparison_summary.md`. +Three-way binary comparison (6.5.171 / 6.5.172 / SIMPLNX) on a hand-built legacy-native cubic/hex mesh, plus SIMPLNX-vs-baked on the 756,474-face real mesh. Full write-up: `vv_work/face_ipf/legacy_comparison_summary.md` in the engineer's verification working folder. + +> **⚠ Evidence archival (open action):** `vv_work/face_ipf/` is not committed to the repository. The comparison pipelines, fixture builder, and `legacy_comparison_summary.md` must be uploaded to the OneDrive verification archive (per the archive-filter-verification workflow) and this note replaced with the archive link before final sign-off. Until then the three-way A/B evidence is not independently reviewable from this repo. - `ComputeFaceIPFColoringFilter-D1` — Phase-2 face side colored with Phase-1's Laue symmetry operator (and left black on `feature1`-invalid boundary faces). The #1635 bug; 6.5.171 reproduces it, 6.5.172 and SIMPLNX fix it. Empirically 120,000/756,474 faces affected on real data. See `vv/deviations/ComputeFaceIPFColoringFilter.md`. - `ComputeFaceIPFColoringFilter-D2` — hex basal IPF hue differs between EbsdLib 3.0.0 (SIMPLNX, green) and legacy EbsdLib (6.5.171 & 6.5.172, blue); a library deviation affecting all hex IPF coloring, flagged for review. diff --git a/src/Plugins/OrientationAnalysis/vv/provenance/ComputeFaceIPFColoringFilter-circular-oracle.md b/src/Plugins/OrientationAnalysis/vv/provenance/ComputeFaceIPFColoringFilter-circular-oracle.md index 1162b49cde..b7c7362121 100644 --- a/src/Plugins/OrientationAnalysis/vv/provenance/ComputeFaceIPFColoringFilter-circular-oracle.md +++ b/src/Plugins/OrientationAnalysis/vv/provenance/ComputeFaceIPFColoringFilter-circular-oracle.md @@ -21,7 +21,7 @@ No Class 2/3/5 provenance block applies (Class 1 oracle lives in the test code). ## Regenerated to fix a circular-oracle situation? -**YES — comparison retired, not regenerated.** Before this V&V cycle the test `OrientationAnalysis::ComputeFaceIPFColoringFilter: Valid filter execution` compared the filter's output against a `SurfaceMeshFaceIPFColors` array baked into the shared archive `6_6_Small_IN100_GBCD.tar.gz`. That baked array was the filter's **own pre-fix output** — a textbook circular oracle: it encoded the issue #1635 bug (notably black Phase-2 colors on `feature1`-invalid boundary faces of the single-phase Small IN100 mesh), so applying the `phase1`→`phase2` fix correctly broke the test. +**YES — comparison retired, not regenerated.** Before this V&V cycle the test `OrientationAnalysis::ComputeFaceIPFColoringFilter: Valid filter execution` compared the filter's output against a `SurfaceMeshFaceIPFColors` array baked into the shared archive `6_6_Small_IN100_GBCD.tar.gz`. That baked array was generated by legacy DREAM3D 6.6-era tooling, which carried the same #1635 bug that was later ported verbatim into SIMPLNX — so it is byte-identical to the filter's own pre-fix output and functioned as a **circular oracle with respect to the defect** (the deviations file's description of it as "authentic 6.5.171-class output" and this file's "own pre-fix output" describe the same array; the two characterizations are equivalent because the bug was ported unchanged). It encoded the bug (notably black Phase-2 colors on `feature1`-invalid boundary faces of the single-phase Small IN100 mesh), so applying the `phase1`→`phase2` fix correctly broke the test. Rather than regenerate the baked array from post-fix output (which would re-establish a circular oracle, and which is impractical because `6_6_Small_IN100_GBCD.tar.gz` is shared by other GBCD tests), the comparison was **retired** and replaced by the inlined Class 1 analytical oracle. Real-data exercise of the algorithm is retained by `ComputeFaceIPFColoringFilter: ColorKey choice reaches algorithm`, which runs the filter on the full Small IN100 GBCD surface mesh without depending on any baked exemplar. diff --git a/vcpkg-configuration.json b/vcpkg-configuration.json index bcb0fc0334..f25ec36eab 100644 --- a/vcpkg-configuration.json +++ b/vcpkg-configuration.json @@ -8,7 +8,7 @@ { "kind": "git", "repository": "https://github.com/bluequartzsoftware/simplnx-registry", - "baseline": "8e5010ce1bac7cb2a2a8722944913e4a56884439", + "baseline": "a2db0689b5c44eaa1941bb6ecad02bdc437a0c02", "packages": [ "benchmark", "blosc", diff --git a/vcpkg.json b/vcpkg.json index 9477e819d7..6996e287ca 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -83,7 +83,7 @@ "dependencies": [ { "name": "ebsdlib", - "version>=": "3.1.0" + "version>=": "3.0.0" } ] }, From 773d830eb0cd8c87cd8981ac6f74ae5b1cee9de2 Mon Sep 17 00:00:00 2001 From: Michael Jackson Date: Wed, 8 Jul 2026 22:00:15 -0400 Subject: [PATCH 4/5] =?UTF-8?q?DOC:=20ComputeFaceIPFColoring=20=E2=80=94?= =?UTF-8?q?=20reframe=20A/B=20evidence=20as=20reproducible=20from=20OneDri?= =?UTF-8?q?ve=20archive?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The A/B fixture builder and comparison pipelines are archived in the OneDrive verification archive (vv_work/face_ipf/); the rendered output snapshots and the prose legacy_comparison_summary.md were not preserved. Reframe the report and deviations so the 120,000-face figure is stated as reproducible by re-running the archived generators + pipelines, rather than citing the missing write-up. The claim is retained (not dropped) because it is corroborating: the phase1->phase2 fix is independently verified by the mutation-tested Class 1 analytical oracle. Signed-off-by: Michael Jackson --- .../vv/ComputeFaceIPFColoringFilter.md | 6 +++--- .../vv/deviations/ComputeFaceIPFColoringFilter.md | 8 ++++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Plugins/OrientationAnalysis/vv/ComputeFaceIPFColoringFilter.md b/src/Plugins/OrientationAnalysis/vv/ComputeFaceIPFColoringFilter.md index c4cf0b20f4..363841ec2d 100644 --- a/src/Plugins/OrientationAnalysis/vv/ComputeFaceIPFColoringFilter.md +++ b/src/Plugins/OrientationAnalysis/vv/ComputeFaceIPFColoringFilter.md @@ -94,11 +94,11 @@ Source: `src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorit ## Deviations from DREAM3D 6.5.171 -Three-way binary comparison (6.5.171 / 6.5.172 / SIMPLNX) on a hand-built legacy-native cubic/hex mesh, plus SIMPLNX-vs-baked on the 756,474-face real mesh. Full write-up: `vv_work/face_ipf/legacy_comparison_summary.md` in the engineer's verification working folder. +Three-way binary comparison (6.5.171 / 6.5.172 / SIMPLNX) on a hand-built legacy-native cubic/hex mesh, plus SIMPLNX-vs-baked on the 756,474-face real mesh. -> **⚠ Evidence archival (open action):** `vv_work/face_ipf/` is not committed to the repository. The comparison pipelines, fixture builder, and `legacy_comparison_summary.md` must be uploaded to the OneDrive verification archive (per the archive-filter-verification workflow) and this note replaced with the archive link before final sign-off. Until then the three-way A/B evidence is not independently reviewable from this repo. +> **Evidence (reproducible from OneDrive archive):** the A/B **fixture builder and comparison pipelines** are archived in the OneDrive verification archive (`vv_work/face_ipf/`); the measured figures below are **reproducible by re-running those generators + pipelines** against DREAM3D 6.5.171 / 6.5.172 / SIMPLNX. The rendered output snapshots and the prose `legacy_comparison_summary.md` write-up were not preserved, so the byte-level figures are reproducible rather than directly archived as result files. This A/B is **corroborating, not load-bearing**: the `phase1`→`phase2` fix is independently verified by the Class 1 analytical oracle (mutation-tested), which is the primary correctness evidence. -- `ComputeFaceIPFColoringFilter-D1` — Phase-2 face side colored with Phase-1's Laue symmetry operator (and left black on `feature1`-invalid boundary faces). The #1635 bug; 6.5.171 reproduces it, 6.5.172 and SIMPLNX fix it. Empirically 120,000/756,474 faces affected on real data. See `vv/deviations/ComputeFaceIPFColoringFilter.md`. +- `ComputeFaceIPFColoringFilter-D1` — Phase-2 face side colored with Phase-1's Laue symmetry operator (and left black on `feature1`-invalid boundary faces). The #1635 bug; 6.5.171 reproduces it, 6.5.172 and SIMPLNX fix it. 120,000/756,474 faces affected on real data (reproducible from the archived A/B generators). See `vv/deviations/ComputeFaceIPFColoringFilter.md`. - `ComputeFaceIPFColoringFilter-D2` — hex basal IPF hue differs between EbsdLib 3.0.0 (SIMPLNX, green) and legacy EbsdLib (6.5.171 & 6.5.172, blue); a library deviation affecting all hex IPF coloring, flagged for review. *Note:* SIMPLNX-written `.dream3d` files are, by design, not readable by legacy DREAM3D 6.5.171 (the FileVersion dataset gates this), so the legacy A/B input was authored directly in legacy format with the `legacy_dream3d` writer — the standard approach for legacy comparisons. diff --git a/src/Plugins/OrientationAnalysis/vv/deviations/ComputeFaceIPFColoringFilter.md b/src/Plugins/OrientationAnalysis/vv/deviations/ComputeFaceIPFColoringFilter.md index 5f7c1c6f33..acecda1a44 100644 --- a/src/Plugins/OrientationAnalysis/vv/deviations/ComputeFaceIPFColoringFilter.md +++ b/src/Plugins/OrientationAnalysis/vv/deviations/ComputeFaceIPFColoringFilter.md @@ -24,7 +24,7 @@ Entries are referenced by stable ID (`ComputeFaceIPFColoringFilter-D`) from t The fix (`phase1`→`phase2`) is confirmed against the Class 1 analytical oracle and matches the legacy 6.5.172 backport `1c96b3b8e`. **Empirical evidence — legacy binary A/B (hand-built mixed cubic/hex mesh).** On a legacy-native -4-face fixture (authored with the `legacy_dream3d` writer; see `vv_work/face_ipf/`), DREAM3D +4-face fixture (authored with the `legacy_dream3d` writer; generators + pipelines archived on OneDrive under `vv_work/face_ipf/`), DREAM3D 6.5.171 reproduces the hand-derived *buggy* oracle exactly: the mixed-phase face's Phase-2 color is cubic red `(255,0,0)` (Phase-1's operator on a hex feature) and the `feature1`-invalid boundary face's Phase-2 color is black `(0,0,0)`. DREAM3D 6.5.172 and SIMPLNX both apply the hex operator on @@ -34,7 +34,11 @@ cubic|cubic control face are identical across all three. **Empirical evidence — real data (756,474-face Small IN100 GBCD mesh).** SIMPLNX (fixed) vs the buggy `SurfaceMeshFaceIPFColors` baked into the file (authentic 6.5.171-class output) differs on **exactly 120,000 faces**, every one a `feature1 ≤ 0` boundary face, **zero** elsewhere (single-phase, -so only the boundary manifestation appears). Full write-up: `vv_work/face_ipf/legacy_comparison_summary.md`. +so only the boundary manifestation appears). This figure is **reproducible** by re-running the A/B +generators + pipelines archived on OneDrive (`vv_work/face_ipf/`); the rendered snapshots and the +prose `legacy_comparison_summary.md` were not preserved, so it is reproducible rather than archived as +a result file. The `phase1`→`phase2` fix itself is verified independently of this A/B by the Class 1 +analytical oracle, so this real-data figure is corroborating. **Affected users:** (a) Anyone coloring surface meshes of **multi-phase** microstructures where adjacent phases use different Laue groups — alpha/beta titanium, dual-phase steels, IN625 with inclusions. (b) **All** users (including single-phase) on exterior/boundary faces where the mesh ordered the exterior side as `feature1`; those faces' second color was silently black in 6.5.171. Users who only inspect the first color, or whose meshes order the real feature as `feature1`, are unaffected. From 8df4a1c86869c10f3a596bc9819cdbff06ac22af Mon Sep 17 00:00:00 2001 From: Michael Jackson Date: Fri, 10 Jul 2026 12:16:14 -0400 Subject: [PATCH 5/5] Revert EbsdLib 3.0.0 override --- vcpkg-configuration.json | 2 +- vcpkg.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/vcpkg-configuration.json b/vcpkg-configuration.json index f25ec36eab..bcb0fc0334 100644 --- a/vcpkg-configuration.json +++ b/vcpkg-configuration.json @@ -8,7 +8,7 @@ { "kind": "git", "repository": "https://github.com/bluequartzsoftware/simplnx-registry", - "baseline": "a2db0689b5c44eaa1941bb6ecad02bdc437a0c02", + "baseline": "8e5010ce1bac7cb2a2a8722944913e4a56884439", "packages": [ "benchmark", "blosc", diff --git a/vcpkg.json b/vcpkg.json index 6996e287ca..9477e819d7 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -83,7 +83,7 @@ "dependencies": [ { "name": "ebsdlib", - "version>=": "3.0.0" + "version>=": "3.1.0" } ] },