From ef89166e5009f9319735c194b06dbee69d895638 Mon Sep 17 00:00:00 2001 From: Jared Duffey Date: Tue, 21 Jul 2026 12:49:02 -0400 Subject: [PATCH 1/9] VV: Compute C Axis Locations fully V&V'ed Summary: * Confirmed no bugs (clean port) * Confirmed no deviations from DREAM3D 6.5.171 * Retired 2 tests * Retired circular oracle test * One test superceded by more specific test * Added Class 1 Oracle test * Test data files replaced with 15 inline test fixtures * Added V&V report, deviations * Added ReferenceFrameNotes.rst to help explain how the filter calculations rotations * Providence file is not necessary due to test archive being replaced by inline data Signed-off-by: Jared Duffey --- docs/ReferenceFrameNotes.rst | 245 ++++++++++++++++++ .../test/ComputeCAxisLocationsTest.cpp | 229 +++++++++++----- .../vv/ComputeCAxisLocationsFilter.md | 102 ++++++++ .../deviations/ComputeCAxisLocationsFilter.md | 11 + 4 files changed, 524 insertions(+), 63 deletions(-) create mode 100644 docs/ReferenceFrameNotes.rst create mode 100644 src/Plugins/OrientationAnalysis/vv/ComputeCAxisLocationsFilter.md create mode 100644 src/Plugins/OrientationAnalysis/vv/deviations/ComputeCAxisLocationsFilter.md diff --git a/docs/ReferenceFrameNotes.rst b/docs/ReferenceFrameNotes.rst new file mode 100644 index 0000000000..1fd69a84be --- /dev/null +++ b/docs/ReferenceFrameNotes.rst @@ -0,0 +1,245 @@ +:topic: Reference Frame Notes + +.. index:: + triple: DREAM3D-NX; Reference Frame; Notes + +Reference Frame Notes +############################### + + +When dealing with **orientation** data such as Euler angles, Quaternions +and Rodrigues vectors, the user will often need to ensure that the +proper convention is followed to transform data from the **sample** +reference frame to the **crystal** reference frame. DREAM3D-NX makes the +following assumptions about reference frames: + +- Only Passive Rotations are used, i.e., rotations between reference + frames; +- A rotations always transforms the **sample** reference frame to the + **crystal** reference frame. + +DREAM3D-NX can represent orientations in seven different forms: + ++---------------+----+------------------------------------------------+ +| Name | A | Number of Components | +| | bb | | +| | r. | | ++===============+====+================================================+ +| Eulers | e | 3 | ++---------------+----+------------------------------------------------+ +| Rodrigues | r | 4 | ++---------------+----+------------------------------------------------+ +| Orientation | o | 3x3 | +| Matrix | | | ++---------------+----+------------------------------------------------+ +| Quaternion | q | 4 (< x, y, z > w) Note the order of the data. | +| | | Vector-Scalar | ++---------------+----+------------------------------------------------+ +| Axis-Angle | a | 4 (< ax0, ax1, ax2 >, w) | ++---------------+----+------------------------------------------------+ +| Cubochoric | c | 3 | ++---------------+----+------------------------------------------------+ +| Homochoric | h | 3 | ++---------------+----+------------------------------------------------+ + +Master Table of Available Conversions +====================================== + +In many cases, a direct transformation is available from one +representation to another; in some cases, an intermediate representation +is used, e.g., from homochoric (h) to Euler (e), DREAM3D-NX first +transforms to an axis angle pair (a), then to an orientation matrix (o), +and finally to Euler angles. + ++--------------------+-------+--------------------+------------+-----------+------------+------------+------------+---------------+ +| From/To | Euler | Orientation Matrix | Axis Angle | Rodrigues | Quaternion | Homochoric | Cubochoric | Stereographic | ++====================+=======+====================+============+===========+============+============+============+===============+ +| Euler | - | X | X | X | X | a | ah | q | ++--------------------+-------+--------------------+------------+-----------+------------+------------+------------+---------------+ +| Orientation Matrix | X | -- | X | e | X | a | ah | q | ++--------------------+-------+--------------------+------------+-----------+------------+------------+------------+---------------+ +| Axis Angle | o | X | -- | X | X | X | h | q | ++--------------------+-------+--------------------+------------+-----------+------------+------------+------------+---------------+ +| Rodrigues | o | a | X | -- | a | X | h | q | ++--------------------+-------+--------------------+------------+-----------+------------+------------+------------+---------------+ +| Quaternion | X | X | X | X | -- | X | h | q | ++--------------------+-------+--------------------+------------+-----------+------------+------------+------------+---------------+ +| Homochoric | ao | a | X | a | a | -- | X | q | ++--------------------+-------+--------------------+------------+-----------+------------+------------+------------+---------------+ +| Cubochoric | hao | ha | h | ha | ha | X | -- | q | ++--------------------+-------+--------------------+------------+-----------+------------+------------+------------+---------------+ +| Stereographic | a | a | X | X | a | X | hc | -- | ++--------------------+-------+--------------------+------------+-----------+------------+------------+------------+---------------+ + +Filters to Assist the User +============================ + +When importing EBSD data through the various readers (.ang, .ctf files), +there are a number of filters that can assist the user in performing the +necessary transformations. The following is a non-exhaustive list of +filters that will help the user perform various manipulations on the +orientation data. + +**NOTE:** When importing data generated by non-EBSD instruments, it is +very important to know what conventions were used to represent the +rotations/orientations; an incorrect interpretation of these conventions +**will** lead to incorrect results. + ++----------+-------------------------------------------------------------+ +| Filter | Discussion | ++==========+=============================================================+ +| Rotate | This will rotate the Crystal Reference frame. Input | +| Euler | Orientation Data is possibly changed | +| R | | +| eference | | +| Frame | | ++----------+-------------------------------------------------------------+ +| Rotate | This will rotate the actual data into new locations in an | +| Sample | Image Geometry array | +| R | | +| eference | | +| Frame | | ++----------+-------------------------------------------------------------+ +| R | DREAM3D-NX expects Rodrigues vectors to have 4 components, | +| odrigues | where the 4th component is the length of the vector. This | +| C | filter will convert 3 component Rodrigues vectors to the | +| onvertor | internal 4 component convention | ++----------+-------------------------------------------------------------+ +| Generate | This uses an input set of Quaternions and generates the | +| Qu | conjugate quaternions. A copy of the input array is made | +| aternion | and changes are made to that array | +| C | | +| onjugate | | ++----------+-------------------------------------------------------------+ +| Generate | This will generate the transpose of an array of 3x3 | +| Ori | matrices. A copy of the input array is made and changes | +| entation | are made to that array | +| Matrix | | +| T | | +| ranspose | | ++----------+-------------------------------------------------------------+ + +Examples of what can go wrong +================================ + +In this section, we show an IPF generated from data imported from a +LabDCT experiment. The LabDCT input data provided 3 component Rodrigues +vectors which were converted to the 4 component internal format. The +LabDCT program generated the IPF on the left, and DREAM3D-NX generated the +IPF on the right, after application of the Rodrigues vector conversion. +Note that while some grain colors match up, most do not. This is because +the LabDCT output was generated with the convention that a rotation +transforms the **Crystal** reference frame to the **Sample** reference +frame, *which is the opposite of what DREAM3D-NX expects*. + ++----------------------------------+-------------------------------------+ +| LabDCT Generated IPF 001 Colors | DREAM3D-NX Generated IPF 001 Colors | ++==================================+=====================================+ +| |Correctly imported orientation | |Incorrectly imported orientation | +| data| | data| | ++----------------------------------+-------------------------------------+ + +While both transformations (Crystal-to-Sample and Sample-to-Crystal) are +passive rotations (they convert reference frames into one another), the +transformations are each other’s inverse. DREAM3D-NX always expects a +rotation angle to be positive and in the interval [0,pi]; thus, +computing the inverse of a rotation involves changing the sign of the +rotation axis unit vector. Depending on the rotation representation, the +inverse of a rotation can then be implemented by computation of: + +- the negative of the 3-component Rodrigues vector (use the Attribute + Array Calculator filter) [note that this also works for the + homochoric and cubochoric representations, and for the axis vector of + the axis-angle pair] +- the conjugate of the Quaternion (Generate Quaternion Conjugate + filter) +- the transpose of the Orientation Matrix (Generate Orientation Matrix + Transpose filter) + +When the input LabDCT Rodrigues vectors are multiplied by -1 **before** +conversion to the internal 4-component format, then the following IPF is +generated, showing perfect agreement with the original LabDCT IPF map. +This example shows that it is absolutely crucial to know and understand +the conventions that were used to generate orientation data in any type +of diffraction experiment **and** to ensure that the correct conversions +are applied upon loading the data into DREAM3D-NX. + +.. figure:: Images/AlCu-485-Transformed.png + :alt: Correctly imported orientation data to conform to DREAM3D-NX’s assumptions + + Correctly imported orientation data to conform to DREAM3D-NX’s + assumptions + +.. |Correctly imported orientation data| image:: Images/AlCu-485-LabDCT.png +.. |Incorrectly imported orientation data| image:: Images/AlCu-485-WRONG.png + +Crystallographic Information +============================== + ++----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ +| # | Point Group (H–M) | Rotation Point Group | Space Group No(s). | Schoenflies | Crystal system | Laue class | Laue Ops | ++====+===================+======================+====================+===============+================+=============+==================+ +| 1 | 1 | 1 | 1 | C₁ | Triclinic | (\bar{1}) | TriclinicOps | ++----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ +| 2 | (\bar{1}) | 1 | 2 | C(_i) | Triclinic | (\bar{1}) | | ++----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ +| 3 | 2 | 2 | 3–5 | C₂ | Monoclinic | 2/m | | ++----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ +| 4 | m | 1 | 6–9 | C(_s) | Monoclinic | 2/m | | ++----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ +| 5 | 2/m | 2 | 10–15 | C(_{2h}) | Monoclinic | 2/m | MonoclinicOps | ++----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ +| 6 | 222 | 222 | 16–24 | D₂ | Orthorhombic | mmm | | ++----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ +| 7 | mm2 | 2 | 25–46 | C(_{2v}) | Orthorhombic | mmm | | ++----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ +| 8 | mmm | 222 | 47–74 | D(_{2h}) | Orthorhombic | mmm | OrthorhombicOps | ++----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ +| 9 | 4 | 4 | 75–80 | C₄ | Tetragonal | 4/m | | ++----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ +| 10 | (\bar{4}) | 2 | 81–82 | S₄ | Tetragonal | 4/m | | ++----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ +| 11 | 4/m | 4 | 83–88 | C(_{4h}) | Tetragonal | 4/m | TetragonalLowOps | ++----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ +| 12 | 422 | 422 | 89–98 | D₄ | Tetragonal | 4/mmm | | ++----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ +| 13 | 4mm | 4 | 99–110 | C(_{4v}) | Tetragonal | 4/mmm | | ++----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ +| 14 | (\bar{4}2m) | 222 | 111–122 | D(_{2d}) | Tetragonal | 4/mmm | | ++----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ +| 15 | 4/mmm | 422 | 123–142 | D(_{4h}) | Tetragonal | 4/mmm | TetragonalOps | ++----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ +| 16 | 3 | 3 | 143–146 | C₃ | Trigonal | (\bar{3}) | | ++----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ +| 17 | (\bar{3}) | 3 | 147–148 | C(_{3i}) (S₆) | Trigonal | (\bar{3}) | TrigonalLowOps | ++----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ +| 18 | 32 | 32 | 149–155 | D₃ | Trigonal | (\bar{3}m) | | ++----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ +| 19 | 3m | 3 | 156–161 | C(_{3v}) | Trigonal | (\bar{3}m) | | ++----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ +| 20 | (\bar{3}m) | 32 | 162–167 | D(_{3d}) | Trigonal | (\bar{3}m) | TrigonalOps | ++----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ +| 21 | 6 | 6 | 168–173 | C₆ | Hexagonal | 6/m | | ++----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ +| 22 | (\bar{6}) | 3 | 174 | C(_{3h}) | Hexagonal | 6/m | | ++----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ +| 23 | 6/m | 6 | 175–176 | C(_{6h}) | Hexagonal | 6/m | HexagonalLowOps | ++----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ +| 24 | 622 | 622 | 177–182 | D₆ | Hexagonal | 6/mmm | | ++----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ +| 25 | 6mm | 6 | 183–186 | C(_{6v}) | Hexagonal | 6/mmm | | ++----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ +| 26 | (\bar{6}m2) | 32 | 187–190 | D(_{3h}) | Hexagonal | 6/mmm | | ++----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ +| 27 | 6/mmm | 622 | 191–194 | D(_{6h}) | Hexagonal | 6/mmm | HexagonalOps | ++----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ +| 28 | 23 | 23 | 195–199 | T | Cubic | m(\bar{3}) | | ++----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ +| 29 | m(\bar{3}) | 23 | 200–206 | T(_h) | Cubic | m(\bar{3}) | CubicLowOps | ++----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ +| 30 | 432 | 432 | 207–214 | O | Cubic | m(\bar{3})m | | ++----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ +| 31 | (\bar{4}3m) | 23 | 215–220 | T(_d) | Cubic | m(\bar{3})m | | ++----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ +| 32 | m(\bar{3})m | 432 | 221–230 | O(_h) | Cubic | m(\bar{3})m | CubicOps | ++----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ diff --git a/src/Plugins/OrientationAnalysis/test/ComputeCAxisLocationsTest.cpp b/src/Plugins/OrientationAnalysis/test/ComputeCAxisLocationsTest.cpp index 1848d59786..a5f2d1446e 100644 --- a/src/Plugins/OrientationAnalysis/test/ComputeCAxisLocationsTest.cpp +++ b/src/Plugins/OrientationAnalysis/test/ComputeCAxisLocationsTest.cpp @@ -2,6 +2,9 @@ #include #include +#include + +#include "simplnx/Common/Constants.hpp" #include "simplnx/Core/Application.hpp" #include "simplnx/DataStructure/AttributeMatrix.hpp" #include "simplnx/DataStructure/Geometry/ImageGeom.hpp" @@ -18,111 +21,211 @@ namespace fs = std::filesystem; namespace { -const std::string k_CAxisLocationsExemplar = "CAxisLocations"; -const std::string k_CAxisLocationsComputed = "NX_CAxisLocations"; +struct FixtureData +{ + std::array inputQuat{}; + std::array expectedOutput{}; +}; + +constexpr float32 k_Sin_OneEighthPiF = 0.38268343f; +constexpr float32 k_Sin_OneEighthPi_OverSqrt3F = k_Sin_OneEighthPiF / k_Sqrt3F; + +const std::vector k_Fixtures = { + // no rotation + {{0.0f, 0.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 1.0f}}, + // +90 about x + {{k_HalfSqrt2F, 0.0f, 0.0f, k_HalfSqrt2F}, {0.0f, 1.0f, 0.0f}}, + // +90 about y + {{0.0f, k_HalfSqrt2F, 0.0f, k_HalfSqrt2F}, {-1.0f, 0.0f, 0.0f}}, + // +90 about z + {{0.0f, 0.0f, k_HalfSqrt2F, k_HalfSqrt2F}, {0.0f, 0.0f, 1.0f}}, + // 180 about x; flip z sign + {{1.0f, 0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}}, + // 180 about y; flip z sign + {{0.0f, 1.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}}, + // 180 about z + {{0.0f, 0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}}, + // 120 about (1, 1, 1) + {{0.5f, 0.5f, 0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}}, + // +45 about x + {{k_Sin_OneEighthPiF, 0.0f, 0.0f, k_Cos_OneEighthPiF}, {0.0f, k_HalfSqrt2F, k_HalfSqrt2F}}, + // +45 about y + {{0.0f, k_Sin_OneEighthPiF, 0.0f, k_Cos_OneEighthPiF}, {-k_HalfSqrt2F, 0.0f, k_HalfSqrt2F}}, + // +45 about z + {{0.0f, 0.0f, k_Sin_OneEighthPiF, k_Cos_OneEighthPiF}, {0.0f, 0.0f, 1.0f}}, + // -45 about x + {{-k_Sin_OneEighthPiF, 0.0f, 0.0f, k_Cos_OneEighthPiF}, {0.0f, -k_HalfSqrt2F, k_HalfSqrt2F}}, + // -45 about y + {{0.0f, -k_Sin_OneEighthPiF, 0.0f, k_Cos_OneEighthPiF}, {k_HalfSqrt2F, 0.0f, k_HalfSqrt2F}}, + // -45 about z + {{0.0f, 0.0f, -k_Sin_OneEighthPiF, k_Cos_OneEighthPiF}, {0.0f, 0.0f, 1.0f}}, + // +45 around (1, 1, 1); v_out = ((2 - sqrt(6) - sqrt(2))/6, (2 + sqrt(6) - sqrt(2))/6, (1 + sqrt(2))/3) + {{k_Sin_OneEighthPi_OverSqrt3F, k_Sin_OneEighthPi_OverSqrt3F, k_Sin_OneEighthPi_OverSqrt3F, k_Cos_OneEighthPiF}, {-0.3106172, 0.50587934, 0.80473787}}, +}; + +template +bool ContainsCode(const std::vector& vec, int32 code) +{ + return std::find_if(vec.cbegin(), vec.cend(), [code](const T& item) { return item.code == code; }) != vec.cend(); +} } // namespace -TEST_CASE("OrientationAnalysis::ComputeCAxisLocationsFilter: Valid Filter Execution", "[OrientationAnalysis][ComputeCAxisLocationsFilter]") +TEST_CASE("OrientationAnalysis::ComputeCAxisLocationsFilter: Preflight Error - Cell array tuple count mismatch (-3520)", "[OrientationAnalysis][ComputeCAxisLocationsFilter][preflight]") { UnitTest::LoadPlugins(); - const nx::core::UnitTest::TestFileSentinel testDataSentinel(nx::core::unit_test::k_TestFilesDir, "caxis_data.tar.gz", "caxis_data"); + // Build a minimal synthetic DataStructure where the two cell-level arrays that are + // validated together (Quats, CellPhases) do NOT share the same tuple count. This drives + // the validateNumberOfTuples() guard in preflightImpl that emits error -3520. + DataStructure dataStructure; + auto* imageGeom = ImageGeom::Create(dataStructure, "DataContainer"); + imageGeom->setDimensions({10, 1, 1}); + + auto* cellAM = AttributeMatrix::Create(dataStructure, "CellData", {10}, imageGeom->getId()); + UnitTest::CreateTestDataArray(dataStructure, "Quats", {10}, {4}, cellAM->getId()); + + // CellPhases lives in a separate AttributeMatrix with a deliberately different tuple + // count (9 != 10) so the cross-array tuple-count check fails. + auto* mismatchAM = AttributeMatrix::Create(dataStructure, "MismatchData", {9}, imageGeom->getId()); + UnitTest::CreateTestDataArray(dataStructure, "Phases", {9}, {1}, mismatchAM->getId()); + + auto* ensembleAM = AttributeMatrix::Create(dataStructure, "CellEnsembleData", {2}, imageGeom->getId()); + UnitTest::CreateTestDataArray(dataStructure, "CrystalStructures", {2}, {1}, ensembleAM->getId()); + + ComputeCAxisLocationsFilter filter; + Arguments args; + args.insertOrAssign(ComputeCAxisLocationsFilter::k_QuatsArrayPath_Key, std::make_any(DataPath({"DataContainer", "CellData", "Quats"}))); + args.insertOrAssign(ComputeCAxisLocationsFilter::k_CellPhasesArrayPath_Key, std::make_any(DataPath({"DataContainer", "MismatchData", "Phases"}))); + args.insertOrAssign(ComputeCAxisLocationsFilter::k_CrystalStructuresArrayPath_Key, std::make_any(DataPath({"DataContainer", "CellEnsembleData", "CrystalStructures"}))); + args.insertOrAssign(ComputeCAxisLocationsFilter::k_CAxisLocationsArrayName_Key, std::make_any("CAxisLocation")); - // Read Exemplar DREAM3D File Filter - auto exemplarFilePath = fs::path(fmt::format("{}/caxis_data/7_0_find_caxis_data.dream3d", unit_test::k_TestFilesDir)); - DataStructure dataStructure = UnitTest::LoadDataStructure(exemplarFilePath); + auto preflightResult = filter.preflight(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_INVALID(preflightResult.outputActions); + REQUIRE(preflightResult.outputActions.errors().size() == 1); + REQUIRE(preflightResult.outputActions.errors()[0].code == -3520); +} + +TEST_CASE("OrientationAnalysis::ComputeCAxisLocationsFilter: No hexagonal phases error", "[OrientationAnalysis][ComputeCAxisLocationsFilter]") +{ + DataStructure dataStructure; + UnitTest::CreateTestDataArray(dataStructure, "Quats", {10}, {4}); + UnitTest::CreateTestDataArray(dataStructure, "Phases", {10}, {1}); + + // Create crystal structures array with no hexagonal phases + UInt32Array* crystalStructures = UnitTest::CreateTestDataArray(dataStructure, "CrystalStructures", {2}, {1}); + crystalStructures->setValue(0, ebsdlib::CrystalStructure::UnknownCrystalStructure); + crystalStructures->setValue(1, ebsdlib::CrystalStructure::Cubic_High); - // Instantiate the filter, a DataStructure object and an Arguments Object ComputeCAxisLocationsFilter filter; Arguments args; - // Create default Parameters for the filter. - args.insertOrAssign(ComputeCAxisLocationsFilter::k_QuatsArrayPath_Key, std::make_any(k_QuatsArrayPath)); - args.insertOrAssign(ComputeCAxisLocationsFilter::k_CellPhasesArrayPath_Key, std::make_any(k_PhasesArrayPath)); - args.insertOrAssign(ComputeCAxisLocationsFilter::k_CrystalStructuresArrayPath_Key, std::make_any(k_CrystalStructuresArrayPath)); - args.insertOrAssign(ComputeCAxisLocationsFilter::k_CAxisLocationsArrayName_Key, std::make_any(k_CAxisLocationsComputed)); + args.insertOrAssign(ComputeCAxisLocationsFilter::k_QuatsArrayPath_Key, std::make_any(std::vector{"Quats"})); + args.insertOrAssign(ComputeCAxisLocationsFilter::k_CellPhasesArrayPath_Key, std::make_any(std::vector{"Phases"})); + args.insertOrAssign(ComputeCAxisLocationsFilter::k_CrystalStructuresArrayPath_Key, std::make_any(std::vector{"CrystalStructures"})); + args.insertOrAssign(ComputeCAxisLocationsFilter::k_CAxisLocationsArrayName_Key, std::make_any("CAxisLocations")); - // Preflight the filter and check result auto preflightResult = filter.preflight(dataStructure, args); - SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions) + 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) - - UnitTest::CompareFloatArraysWithNans(dataStructure, k_CellAttributeMatrix.createChildPath(k_CAxisLocationsExemplar), k_CellAttributeMatrix.createChildPath(k_CAxisLocationsComputed), - UnitTest::EPSILON, false); + SIMPLNX_RESULT_REQUIRE_INVALID(executeResult.result); - UnitTest::CheckArraysInheritTupleDims(dataStructure); + REQUIRE(ContainsCode(executeResult.result.errors(), -3522)); } -TEST_CASE("OrientationAnalysis::ComputeCAxisLocationsFilter: InValid Filter Execution") +TEST_CASE("OrientationAnalysis::ComputeCAxisLocationsFilter: Not all hexagonal phases warning", "[OrientationAnalysis][ComputeCAxisLocationsFilter]") { - UnitTest::LoadPlugins(); + DataStructure dataStructure; + UnitTest::CreateTestDataArray(dataStructure, "Quats", {10}, {4}); - const nx::core::UnitTest::TestFileSentinel testDataSentinel(nx::core::unit_test::k_TestFilesDir, "caxis_data.tar.gz", "caxis_data"); + // Create crystal structures array with some non-hexagonal phases + UInt32Array* crystalStructures = UnitTest::CreateTestDataArray(dataStructure, "CrystalStructures", {3}, {1}); + crystalStructures->setValue(0, ebsdlib::CrystalStructure::UnknownCrystalStructure); + crystalStructures->setValue(1, ebsdlib::CrystalStructure::Hexagonal_High); + crystalStructures->setValue(2, ebsdlib::CrystalStructure::Cubic_High); - // Read Exemplar DREAM3D File Filter - auto exemplarFilePath = fs::path(fmt::format("{}/caxis_data/7_0_find_caxis_data.dream3d", unit_test::k_TestFilesDir)); - DataStructure dataStructure = UnitTest::LoadDataStructure(exemplarFilePath); + // All non-hexagonal phases + Int32Array* phases = UnitTest::CreateTestDataArray(dataStructure, "Phases", {10}, {1}); + for(usize i = 0; i < phases->getSize(); i++) + { + phases->setValue(i, 2); + } - auto& crystalStructs = dataStructure.getDataRefAs(k_CrystalStructuresArrayPath); - crystalStructs[1] = 1; + DataPath cAxisLocationsPath({"CAxisLocations"}); - // Instantiate the filter, a DataStructure object and an Arguments Object ComputeCAxisLocationsFilter filter; Arguments args; - // Create default Parameters for the filter. - args.insertOrAssign(ComputeCAxisLocationsFilter::k_QuatsArrayPath_Key, std::make_any(k_QuatsArrayPath)); - args.insertOrAssign(ComputeCAxisLocationsFilter::k_CellPhasesArrayPath_Key, std::make_any(k_PhasesArrayPath)); - args.insertOrAssign(ComputeCAxisLocationsFilter::k_CrystalStructuresArrayPath_Key, std::make_any(k_CrystalStructuresArrayPath)); - args.insertOrAssign(ComputeCAxisLocationsFilter::k_CAxisLocationsArrayName_Key, std::make_any(k_CAxisLocationsComputed)); + args.insertOrAssign(ComputeCAxisLocationsFilter::k_QuatsArrayPath_Key, std::make_any(std::vector{"Quats"})); + args.insertOrAssign(ComputeCAxisLocationsFilter::k_CellPhasesArrayPath_Key, std::make_any(std::vector{"Phases"})); + args.insertOrAssign(ComputeCAxisLocationsFilter::k_CrystalStructuresArrayPath_Key, std::make_any(std::vector{"CrystalStructures"})); + args.insertOrAssign(ComputeCAxisLocationsFilter::k_CAxisLocationsArrayName_Key, std::make_any(cAxisLocationsPath.getTargetName())); - // Preflight the filter and check result auto preflightResult = filter.preflight(dataStructure, args); - SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions) + SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions); - // Execute the filter and check the result auto executeResult = filter.execute(dataStructure, args); - SIMPLNX_RESULT_REQUIRE_INVALID(executeResult.result) + SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result); + + REQUIRE(ContainsCode(executeResult.result.warnings(), -3523)); - UnitTest::CheckArraysInheritTupleDims(dataStructure); + REQUIRE(dataStructure.containsData(cAxisLocationsPath)); + const auto& cAxisLocations = dataStructure.getDataRefAs(cAxisLocationsPath); + REQUIRE(std::all_of(cAxisLocations.cbegin(), cAxisLocations.cend(), [](float32 value) { return std::isnan(value); })); } -TEST_CASE("OrientationAnalysis::ComputeCAxisLocationsFilter: Preflight Error - Cell array tuple count mismatch (-3520)", "[OrientationAnalysis][ComputeCAxisLocationsFilter][preflight]") +TEST_CASE("OrientationAnalysis::ComputeCAxisLocationsFilter: Class 1 Oracle", "[OrientationAnalysis][ComputeCAxisLocationsFilter]") { - UnitTest::LoadPlugins(); - - // Build a minimal synthetic DataStructure where the two cell-level arrays that are - // validated together (Quats, CellPhases) do NOT share the same tuple count. This drives - // the validateNumberOfTuples() guard in preflightImpl that emits error -3520. DataStructure dataStructure; - auto* imageGeom = ImageGeom::Create(dataStructure, "DataContainer"); - imageGeom->setDimensions({10, 1, 1}); - auto* cellAM = AttributeMatrix::Create(dataStructure, "CellData", {10}, imageGeom->getId()); - UnitTest::CreateTestDataArray(dataStructure, "Quats", {10}, {4}, cellAM->getId()); + const usize size = k_Fixtures.size(); - // CellPhases lives in a separate AttributeMatrix with a deliberately different tuple - // count (9 != 10) so the cross-array tuple-count check fails. - auto* mismatchAM = AttributeMatrix::Create(dataStructure, "MismatchData", {9}, imageGeom->getId()); - UnitTest::CreateTestDataArray(dataStructure, "Phases", {9}, {1}, mismatchAM->getId()); + Float32Array* quats = UnitTest::CreateTestDataArray(dataStructure, "Quats", {size}, {4}); - auto* ensembleAM = AttributeMatrix::Create(dataStructure, "CellEnsembleData", {2}, imageGeom->getId()); - UnitTest::CreateTestDataArray(dataStructure, "CrystalStructures", {2}, {1}, ensembleAM->getId()); + for(usize i = 0; i < size; i++) + { + for(usize j = 0; j < 4; j++) + { + quats->setComponent(i, j, k_Fixtures[i].inputQuat[j]); + } + } + + UInt32Array* crystalStructures = UnitTest::CreateTestDataArray(dataStructure, "CrystalStructures", {2}, {1}); + crystalStructures->setValue(0, ebsdlib::CrystalStructure::UnknownCrystalStructure); + crystalStructures->setValue(1, ebsdlib::CrystalStructure::Hexagonal_High); + + Int32Array* phases = UnitTest::CreateTestDataArray(dataStructure, "Phases", {size}, {1}); + for(usize i = 0; i < phases->getSize(); i++) + { + phases->setValue(i, 1); + } + + DataPath cAxisLocationsPath({"CAxisLocations"}); ComputeCAxisLocationsFilter filter; Arguments args; - args.insertOrAssign(ComputeCAxisLocationsFilter::k_QuatsArrayPath_Key, std::make_any(DataPath({"DataContainer", "CellData", "Quats"}))); - args.insertOrAssign(ComputeCAxisLocationsFilter::k_CellPhasesArrayPath_Key, std::make_any(DataPath({"DataContainer", "MismatchData", "Phases"}))); - args.insertOrAssign(ComputeCAxisLocationsFilter::k_CrystalStructuresArrayPath_Key, std::make_any(DataPath({"DataContainer", "CellEnsembleData", "CrystalStructures"}))); - args.insertOrAssign(ComputeCAxisLocationsFilter::k_CAxisLocationsArrayName_Key, std::make_any("CAxisLocation")); + + args.insertOrAssign(ComputeCAxisLocationsFilter::k_QuatsArrayPath_Key, std::make_any(std::vector{"Quats"})); + args.insertOrAssign(ComputeCAxisLocationsFilter::k_CellPhasesArrayPath_Key, std::make_any(std::vector{"Phases"})); + args.insertOrAssign(ComputeCAxisLocationsFilter::k_CrystalStructuresArrayPath_Key, std::make_any(std::vector{"CrystalStructures"})); + args.insertOrAssign(ComputeCAxisLocationsFilter::k_CAxisLocationsArrayName_Key, std::make_any(cAxisLocationsPath.getTargetName())); auto preflightResult = filter.preflight(dataStructure, args); - SIMPLNX_RESULT_REQUIRE_INVALID(preflightResult.outputActions); - REQUIRE(preflightResult.outputActions.errors().size() == 1); - REQUIRE(preflightResult.outputActions.errors()[0].code == -3520); + SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions); + + auto executeResult = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result); + + REQUIRE(dataStructure.containsData(cAxisLocationsPath)); + auto& cAxisLocations = dataStructure.getDataRefAs(cAxisLocationsPath); + for(usize i = 0; i < size; i++) + { + for(usize j = 0; j < 3; j++) + { + INFO(fmt::format("i = {} | j = {} | input_quat = ({}) | expected_vec = ({})", i, j, fmt::join(k_Fixtures[i].inputQuat, ", "), fmt::join(k_Fixtures[i].expectedOutput, ", "))); + REQUIRE(cAxisLocations.getComponent(i, j) == Approx(k_Fixtures[i].expectedOutput[j])); + } + } } TEST_CASE("OrientationAnalysis::ComputeCAxisLocationsFilter: SIMPL Backwards Compatibility", "[OrientationAnalysis][ComputeCAxisLocationsFilter][BackwardsCompatibility]") diff --git a/src/Plugins/OrientationAnalysis/vv/ComputeCAxisLocationsFilter.md b/src/Plugins/OrientationAnalysis/vv/ComputeCAxisLocationsFilter.md new file mode 100644 index 0000000000..ef30426762 --- /dev/null +++ b/src/Plugins/OrientationAnalysis/vv/ComputeCAxisLocationsFilter.md @@ -0,0 +1,102 @@ +# V&V Report: ComputeCAxisLocationsFilter + +| | | +|--------|--------------| +| Plugin | OrientationAnalysis | +| SIMPLNX UUID | `a51c257a-ddc1-499a-9b21-f2d25a19d098` | +| DREAM3D 6.5.171 equivalent | `FindCAxisLocations` (SIMPL UUID `68ae7b7e-b9f7-5799-9f82-ce21d0ccd55e`) - `Source/Plugins/OrientationAnalysis/OrientationAnalysisFilters/FindCAxisLocations.{h,cpp}` | +| Verified commit | ** | +| Status | READY FOR REVIEW | +| Sign-off | ** | + +## At a glance + +A scannable dashboard for reviewers. Each row is one sentence to one short paragraph — enough that a reader can decide whether they need to read the long-form sections below. + +| Aspect | Current state | +|------------------------|------------------------------------------------------------------------------------------------------------------------------| +| Algorithm Relationship | Port - The EbsdLib, matrix math, and SIMPL APIs have changed but the code is functionally identical. Addition of several error branchs when the crystal structure type is not hexagonal. | +| Oracle (confirmed) | Class 1 (Analytical) - 2 hand derived data fixtures | +| Code paths enumerated | 6 of 7 paths exercised - only the filter cancelation path is untested | +| Tests today | 5 test cases - 1 test with Class 1 Oracle, 2 error path tests, 1 warning path test, 1 SIMPL json backwards compatibility test | +| Exemplar archive | None - removed test using circular oracle data from `caxis_data.tar.gz` | +| Legacy comparison | Run against DREAM3D 6.5.171 with the inline test data for the Class 1 Oracle. Result is bit-identical between 6.5.171 and NX | +| Bug flags | None | +| V&V phase | Ready for review | + +For worked instances see `src/Plugins/OrientationAnalysis/vv/BadDataNeighborOrientationCheckFilter.md` and `src/Plugins/OrientationAnalysis/vv/ComputeAvgCAxesFilter.md` (on `topic/vv/compute_avg_caxis`). + +## Summary + +ComputeCAxisLocationsFilter determines the direction of the C-axis for each element, in the *sample reference frame*, by applying the quaternion of the element to the <001> direction, which is the C-axis for *Hexagonal* materials. + +The filter is verified with a Class 1 (Analytical) oracle. The filter uses the quaternion to rotate the C-axis into the sample reference frame. This is done by converting the quaternion to a rotation matrix. Then the transpose of the matrix is used due to DREAM3D conventions (see docs/ReferenceFrameNotes.rst). Due to the transpose the sign of the third element may need to be flipped. A new test was added with handed verified data. + +There were no deviations that affect the output found for hexagonal materials. + +## Algorithm Relationship + +*Classification*: Port + +*Evidence*: Same loop with the same rotation equation used + +- UUID changed from SIMPL and filter renamed to match "Compute" naming conventions. +- EbsdLib is now up to version 3 which has equivalent but changed API for orientations like quaternions. The internal matrix math API has also changed here to use EbsdLib and Eigen but is functionally identical. +- Added one error branch before main execution checking for at least one hexagonal phase +- Added one warning branch before main execution for when not all phases are hexagonal +- Added branch inside loop to set not hexagonal quaternions to NAN to identify them as invalid + +*PR(s):* + +- **PR #576** ("FILTER: FindCAxisLocations & FindFeatureNeighborCAxisMisalignment filters") - Initial PR +- **PR #801** ("ENH: Rename complex to simplnx") - Library rename +- **PR #956** ("ENH: Rename Filters that start with Find/Generate/Calculate to Compute") - Filter rename +- **PR #1438** ("ENH: Microtexture related filter cleanup") - Header include changed +- **PR #1472** ("ENH: Update to EbsdLib 2.0.0 API") - EbsdLib 2.0.0 API update +- **PR #1582** ("ENH: Add missing cancel checks to lots of filters") - Added cancel check in loop + +## Oracle + +*Class:* **Class 1 (Analytical)** + +*Applied:* Handed derived output of C-axis locations from quaternions. The expected outputs agree between DREAM3DNX, DREAM3D 6.5.171, and manual calculations (`v_passive ​= Rᵀv` with z component forced to postive). Includes 15 different orientations about x, y, and z at different angles. The using the previous formula the exact form results were produced and compared against DREAM3D output. + +*Encoded:* *`test/ComputeCAxisLocationsTest.cpp::"OrientationAnalysis::ComputeCAxisLocationsFilter: Class 1 Oracle"` - 15 fixtures, all pass.* + +*Second-engineer review:* *Pending* + +## Code path coverage + +*6 of 7 paths exercised. The non-covered path is the cancellation branch which is not currently able to be tested for all filters* + +Source: `src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/.cpp` (N lines). + +| # | Phase | Path | Test case | +|----|-----------------|---------------------------------------------------|--------------------------------------------| +| 1 | *Preflight* | Tuple validity check (-3520 error code) | "OrientationAnalysis::ComputeCAxisLocationsFilter: Preflight Error - Cell array tuple count mismatch (-3520)" | +| 2 | *Execute* | No hexagonal phases check (-3522 error code) | "OrientationAnalysis::ComputeCAxisLocationsFilter: No hexagonal phases error" | +| 3 | *Execute* | Not all phases hexagonal check (-3523 warning code) | "OrientationAnalysis::ComputeCAxisLocationsFilter: Not all hexagonal phases warning" | +| 4 | *Execute - per-cell* | Should cancel check | Not directly tested - no filter cancellation testing infrastructure | +| 5 | *Execute - per-cell* | Hexagonal C-axis location calculation path | "OrientationAnalysis::ComputeCAxisLocationsFilter: Class 1 Oracle" | +| 6 | *Execute - per-cell* | C-axis direction flip check | "OrientationAnalysis::ComputeCAxisLocationsFilter: Class 1 Oracle" | +| 7 | *Execute - per-cell* | Non-hexagonal NAN branch | "OrientationAnalysis::ComputeCAxisLocationsFilter: Not all hexagonal phases warning" | + +## Test inventory + +| Test case | Status | Notes | +|-----------|--------|-------| +| "OrientationAnalysis::ComputeCAxisLocationsFilter: Valid Filter Execution" | retired | Circular oracle | +| "OrientationAnalysis::ComputeCAxisLocationsFilter: InValid Filter Execution" | retired | Superceded by more specific test "OrientationAnalysis::ComputeCAxisLocationsFilter: No hexagonal phases error" | +| "OrientationAnalysis::ComputeCAxisLocationsFilter: Preflight Error - Cell array tuple count mismatch (-3520)" | kept | Covers preflight error for tuple mismatch of phases and quats | +| "OrientationAnalysis::ComputeCAxisLocationsFilter: SIMPL Backwards Compatibility" | kept | Covers SIMPL json backwards compatibility | +| "OrientationAnalysis::ComputeCAxisLocationsFilter: No hexagonal phases error" | new-for-V&V | Covers no hexagonal phases branch | +| "OrientationAnalysis::ComputeCAxisLocationsFilter: Not all hexagonal phases warning" | new-for-V&V | Covers non-hexagonal branch | +| "OrientationAnalysis::ComputeCAxisLocationsFilter: Class 1 Oracle" | new-for-V&V | Covers hand calculated quaternions which also agree with DREAM3D 6.5.171. Also covers the sign flip path. | + +## Exemplar archive + +No new exemplar archive was created for this V&V cycle: the Class 1 oracle is encoded entirely as inline expected values in the test source. Tests no longer use circular oracle `caxis_data.tar.gz`. + +## Deviations from DREAM3D 6.5.171 + +- No deviations observed. Comparison run on "OrientationAnalysis::ComputeCAxisLocationsFilter: Class 1 Oracle" between NX and 6.5.171 with bit-identical output. diff --git a/src/Plugins/OrientationAnalysis/vv/deviations/ComputeCAxisLocationsFilter.md b/src/Plugins/OrientationAnalysis/vv/deviations/ComputeCAxisLocationsFilter.md new file mode 100644 index 0000000000..8bf5a1012c --- /dev/null +++ b/src/Plugins/OrientationAnalysis/vv/deviations/ComputeCAxisLocationsFilter.md @@ -0,0 +1,11 @@ +# Deviations from DREAM3D 6.5.171: ComputeCAxisLocationsFilter + +This file lists every documented behavioral difference between this SIMPLNX filter and its DREAM3D 6.5.171 equivalent. + +Entries are referenced by stable ID (`ComputeCAxisLocationsFilter-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. + +--- + +## Headline: No deviations observed + +The legacy comparison used the inline data **"OrientationAnalysis::ComputeCAxisLocationsFilter: Class 1 Oracle"**: These inline quaternion values were encoded into a csv file. This was then read into a 6.5.171 pipeline with the `FindCAxisLocations` filter and wrote out to a DREAM3D file. The output of NX test was also run and written to a file. These outputs were then verified to have no differences by using `h5py` to read both files and compare the values. From 9e5ecececa39a8fc578babc3f685dd34a5d08318 Mon Sep 17 00:00:00 2001 From: Michael Jackson Date: Wed, 22 Jul 2026 21:05:53 -0400 Subject: [PATCH 2/9] REV: Remove A/B comparison artifacts from vv/comparisons * Comparison pipelines and results are working evidence, not source-tree deliverables; the distilled numbers belong in the vv/deviations entries * Removes the ConvertOrientationsFilter pipelines and comparison report that should not have been committed Signed-off-by: Michael Jackson --- .../pipelines/legacy_6_5_171.json | 129 ------------------ .../pipelines/nx.d3dpipeline | 96 ------------- .../results/.gitignore | 3 - .../results/comparison.md | 33 ----- 4 files changed, 261 deletions(-) delete mode 100644 src/Plugins/OrientationAnalysis/vv/comparisons/ConvertOrientationsFilter/pipelines/legacy_6_5_171.json delete mode 100644 src/Plugins/OrientationAnalysis/vv/comparisons/ConvertOrientationsFilter/pipelines/nx.d3dpipeline delete mode 100644 src/Plugins/OrientationAnalysis/vv/comparisons/ConvertOrientationsFilter/results/.gitignore delete mode 100644 src/Plugins/OrientationAnalysis/vv/comparisons/ConvertOrientationsFilter/results/comparison.md diff --git a/src/Plugins/OrientationAnalysis/vv/comparisons/ConvertOrientationsFilter/pipelines/legacy_6_5_171.json b/src/Plugins/OrientationAnalysis/vv/comparisons/ConvertOrientationsFilter/pipelines/legacy_6_5_171.json deleted file mode 100644 index 279ff43d1b..0000000000 --- a/src/Plugins/OrientationAnalysis/vv/comparisons/ConvertOrientationsFilter/pipelines/legacy_6_5_171.json +++ /dev/null @@ -1,129 +0,0 @@ -{ - "0": { - "FilterVersion": "1.0.0", - "Filter_Human_Label": "Create Data Container", - "Filter_Name": "CreateDataContainer", - "Filter_Uuid": "{816fbe6b-7c38-581b-b149-3f839fb65b93}", - "DataContainerName": "DataContainer" - }, - "1": { - "AttributeMatrixType": 3, - "CreatedAttributeMatrix": { "Attribute Matrix Name": "CellData", "Data Array Name": "", "Data Container Name": "DataContainer" }, - "FilterVersion": "1.1.572", - "Filter_Human_Label": "Create Attribute Matrix", - "Filter_Name": "CreateAttributeMatrix", - "Filter_Uuid": "{93375ef0-7367-5372-addc-baa019b1b341}", - "TupleDimensions": { - "Column Headers": ["0"], - "DefaultColCount": 0, "DefaultRowCount": 0, - "HasDynamicCols": true, "HasDynamicRows": false, - "MinColCount": 0, "MinRowCount": 0, - "Row Headers": ["0"], - "Table Data": [[1]] - } - }, - "2": { - "FilterVersion": "1.0.359", - "Filter_Human_Label": "Create Data Array", - "Filter_Name": "CreateDataArray", - "Filter_Uuid": "{77f392fb-c1eb-57da-a1b1-e7acf9239fb8}", - "InitializationRange": { "Max": 0, "Min": 0 }, - "InitializationType": 0, - "InitializationValue": "0.785398185", - "NewArray": { "Attribute Matrix Name": "CellData", "Data Array Name": "eu0", "Data Container Name": "DataContainer" }, - "NumberOfComponents": 1, - "ScalarType": 8 - }, - "3": { - "FilterVersion": "1.0.359", - "Filter_Human_Label": "Create Data Array", - "Filter_Name": "CreateDataArray", - "Filter_Uuid": "{77f392fb-c1eb-57da-a1b1-e7acf9239fb8}", - "InitializationRange": { "Max": 0, "Min": 0 }, - "InitializationType": 0, - "InitializationValue": "0.52359879", - "NewArray": { "Attribute Matrix Name": "CellData", "Data Array Name": "eu1", "Data Container Name": "DataContainer" }, - "NumberOfComponents": 1, - "ScalarType": 8 - }, - "4": { - "FilterVersion": "1.0.359", - "Filter_Human_Label": "Create Data Array", - "Filter_Name": "CreateDataArray", - "Filter_Uuid": "{77f392fb-c1eb-57da-a1b1-e7acf9239fb8}", - "InitializationRange": { "Max": 0, "Min": 0 }, - "InitializationType": 0, - "InitializationValue": "1.04719758", - "NewArray": { "Attribute Matrix Name": "CellData", "Data Array Name": "eu2", "Data Container Name": "DataContainer" }, - "NumberOfComponents": 1, - "ScalarType": 8 - }, - "5": { - "FilterVersion": "1.0.132", - "Filter_Human_Label": "Combine Attribute Arrays", - "Filter_Name": "CombineAttributeArrays", - "NormalizeData": 0, - "SelectedDataArrayPaths": [ - { "Attribute Matrix Name": "CellData", "Data Array Name": "eu0", "Data Container Name": "DataContainer" }, - { "Attribute Matrix Name": "CellData", "Data Array Name": "eu1", "Data Container Name": "DataContainer" }, - { "Attribute Matrix Name": "CellData", "Data Array Name": "eu2", "Data Container Name": "DataContainer" } - ], - "StackedDataArrayName": "EulerAngles" - }, - "6": { - "FilterVersion": "1.0.0", "Filter_Human_Label": "Convert Orientation Representation", "Filter_Name": "ConvertOrientations", - "Filter_Uuid": "{e5629880-98c4-5656-82b8-c9fe2b9744de}", - "InputType": 0, "OutputType": 1, - "InputOrientationArrayPath": { "Attribute Matrix Name": "CellData", "Data Array Name": "EulerAngles", "Data Container Name": "DataContainer" }, - "OutputOrientationArrayName": "leg_om" - }, - "7": { - "FilterVersion": "1.0.0", "Filter_Human_Label": "Convert Orientation Representation", "Filter_Name": "ConvertOrientations", - "Filter_Uuid": "{e5629880-98c4-5656-82b8-c9fe2b9744de}", - "InputType": 0, "OutputType": 2, - "InputOrientationArrayPath": { "Attribute Matrix Name": "CellData", "Data Array Name": "EulerAngles", "Data Container Name": "DataContainer" }, - "OutputOrientationArrayName": "leg_qu" - }, - "8": { - "FilterVersion": "1.0.0", "Filter_Human_Label": "Convert Orientation Representation", "Filter_Name": "ConvertOrientations", - "Filter_Uuid": "{e5629880-98c4-5656-82b8-c9fe2b9744de}", - "InputType": 0, "OutputType": 3, - "InputOrientationArrayPath": { "Attribute Matrix Name": "CellData", "Data Array Name": "EulerAngles", "Data Container Name": "DataContainer" }, - "OutputOrientationArrayName": "leg_ax" - }, - "9": { - "FilterVersion": "1.0.0", "Filter_Human_Label": "Convert Orientation Representation", "Filter_Name": "ConvertOrientations", - "Filter_Uuid": "{e5629880-98c4-5656-82b8-c9fe2b9744de}", - "InputType": 0, "OutputType": 4, - "InputOrientationArrayPath": { "Attribute Matrix Name": "CellData", "Data Array Name": "EulerAngles", "Data Container Name": "DataContainer" }, - "OutputOrientationArrayName": "leg_ro" - }, - "10": { - "FilterVersion": "1.0.0", "Filter_Human_Label": "Convert Orientation Representation", "Filter_Name": "ConvertOrientations", - "Filter_Uuid": "{e5629880-98c4-5656-82b8-c9fe2b9744de}", - "InputType": 0, "OutputType": 5, - "InputOrientationArrayPath": { "Attribute Matrix Name": "CellData", "Data Array Name": "EulerAngles", "Data Container Name": "DataContainer" }, - "OutputOrientationArrayName": "leg_ho" - }, - "11": { - "FilterVersion": "1.0.0", "Filter_Human_Label": "Convert Orientation Representation", "Filter_Name": "ConvertOrientations", - "Filter_Uuid": "{e5629880-98c4-5656-82b8-c9fe2b9744de}", - "InputType": 0, "OutputType": 6, - "InputOrientationArrayPath": { "Attribute Matrix Name": "CellData", "Data Array Name": "EulerAngles", "Data Container Name": "DataContainer" }, - "OutputOrientationArrayName": "leg_cu" - }, - "12": { - "FilterVersion": "1.2.724", - "Filter_Human_Label": "Write DREAM.3D Data File", - "Filter_Name": "DataContainerWriter", - "Filter_Uuid": "{3fcd4c43-9d75-5b86-aad4-4441bc914f37}", - "OutputFile": "/Users/mjackson/Workspace9/simplnx/src/Plugins/OrientationAnalysis/vv/comparisons/ConvertOrientationsFilter/results/legacy_out.dream3d", - "WriteTimeSeries": 0, - "WriteXdmfFile": 0 - }, - "PipelineBuilder": { - "Name": "ConvertOrientations Legacy A/B", - "Number_Filters": 13, - "Version": 6 - } -} diff --git a/src/Plugins/OrientationAnalysis/vv/comparisons/ConvertOrientationsFilter/pipelines/nx.d3dpipeline b/src/Plugins/OrientationAnalysis/vv/comparisons/ConvertOrientationsFilter/pipelines/nx.d3dpipeline deleted file mode 100644 index 22866c6eeb..0000000000 --- a/src/Plugins/OrientationAnalysis/vv/comparisons/ConvertOrientationsFilter/pipelines/nx.d3dpipeline +++ /dev/null @@ -1,96 +0,0 @@ -{ - "isDisabled": false, - "name": "nx.d3dpipeline", - "pinnedParams": [], - "workflowParams": [], - "pipeline": [ - { - "args": { - "import_data_object": { - "value": { "data_paths": [], "file_path": "/Users/mjackson/Workspace9/simplnx/src/Plugins/OrientationAnalysis/vv/comparisons/ConvertOrientationsFilter/results/legacy_out.dream3d", "path_import_policy": 0 }, - "version": 2 - }, - "parameters_version": 1 - }, - "comments": "Read the legacy 6.5.171 output so the EulerAngles input is byte-identical to the legacy run.", - "filter": { "name": "nx::core::ReadDREAM3DFilter", "uuid": "0dbd31c7-19e0-4077-83ef-f4a6459a0e2d" }, - "isDisabled": false - }, - { - "args": { - "input_orientation_array_path": { "value": "DataContainer/CellData/EulerAngles", "version": 1 }, - "input_representation_index": { "value": 0, "version": 1 }, - "output_orientation_array_name": { "value": "nx_om", "version": 1 }, - "output_representation_index": { "value": 1, "version": 1 }, - "parameters_version": 1 - }, - "filter": { "name": "nx::core::ConvertOrientationsFilter", "uuid": "501e54e6-a66f-4eeb-ae37-00e649c00d4b" }, - "isDisabled": false - }, - { - "args": { - "input_orientation_array_path": { "value": "DataContainer/CellData/EulerAngles", "version": 1 }, - "input_representation_index": { "value": 0, "version": 1 }, - "output_orientation_array_name": { "value": "nx_qu", "version": 1 }, - "output_representation_index": { "value": 2, "version": 1 }, - "parameters_version": 1 - }, - "filter": { "name": "nx::core::ConvertOrientationsFilter", "uuid": "501e54e6-a66f-4eeb-ae37-00e649c00d4b" }, - "isDisabled": false - }, - { - "args": { - "input_orientation_array_path": { "value": "DataContainer/CellData/EulerAngles", "version": 1 }, - "input_representation_index": { "value": 0, "version": 1 }, - "output_orientation_array_name": { "value": "nx_ax", "version": 1 }, - "output_representation_index": { "value": 3, "version": 1 }, - "parameters_version": 1 - }, - "filter": { "name": "nx::core::ConvertOrientationsFilter", "uuid": "501e54e6-a66f-4eeb-ae37-00e649c00d4b" }, - "isDisabled": false - }, - { - "args": { - "input_orientation_array_path": { "value": "DataContainer/CellData/EulerAngles", "version": 1 }, - "input_representation_index": { "value": 0, "version": 1 }, - "output_orientation_array_name": { "value": "nx_ro", "version": 1 }, - "output_representation_index": { "value": 4, "version": 1 }, - "parameters_version": 1 - }, - "filter": { "name": "nx::core::ConvertOrientationsFilter", "uuid": "501e54e6-a66f-4eeb-ae37-00e649c00d4b" }, - "isDisabled": false - }, - { - "args": { - "input_orientation_array_path": { "value": "DataContainer/CellData/EulerAngles", "version": 1 }, - "input_representation_index": { "value": 0, "version": 1 }, - "output_orientation_array_name": { "value": "nx_ho", "version": 1 }, - "output_representation_index": { "value": 5, "version": 1 }, - "parameters_version": 1 - }, - "filter": { "name": "nx::core::ConvertOrientationsFilter", "uuid": "501e54e6-a66f-4eeb-ae37-00e649c00d4b" }, - "isDisabled": false - }, - { - "args": { - "input_orientation_array_path": { "value": "DataContainer/CellData/EulerAngles", "version": 1 }, - "input_representation_index": { "value": 0, "version": 1 }, - "output_orientation_array_name": { "value": "nx_cu", "version": 1 }, - "output_representation_index": { "value": 6, "version": 1 }, - "parameters_version": 1 - }, - "filter": { "name": "nx::core::ConvertOrientationsFilter", "uuid": "501e54e6-a66f-4eeb-ae37-00e649c00d4b" }, - "isDisabled": false - }, - { - "args": { - "export_file_path": { "value": "/Users/mjackson/Workspace9/simplnx/src/Plugins/OrientationAnalysis/vv/comparisons/ConvertOrientationsFilter/results/nx_out.dream3d", "version": 1 }, - "parameters_version": 1, - "write_xdmf_file": { "value": false, "version": 1 } - }, - "filter": { "name": "nx::core::WriteDREAM3DFilter", "uuid": "b3a95784-2ced-41ec-8d3d-0242ac130003" }, - "isDisabled": false - } - ], - "version": 1 -} diff --git a/src/Plugins/OrientationAnalysis/vv/comparisons/ConvertOrientationsFilter/results/.gitignore b/src/Plugins/OrientationAnalysis/vv/comparisons/ConvertOrientationsFilter/results/.gitignore deleted file mode 100644 index 90f47baddc..0000000000 --- a/src/Plugins/OrientationAnalysis/vv/comparisons/ConvertOrientationsFilter/results/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Regenerable A/B comparison outputs — reproduce via ../pipelines (see comparison.md) -*.dream3d -*.xdmf diff --git a/src/Plugins/OrientationAnalysis/vv/comparisons/ConvertOrientationsFilter/results/comparison.md b/src/Plugins/OrientationAnalysis/vv/comparisons/ConvertOrientationsFilter/results/comparison.md deleted file mode 100644 index f1a5c8132e..0000000000 --- a/src/Plugins/OrientationAnalysis/vv/comparisons/ConvertOrientationsFilter/results/comparison.md +++ /dev/null @@ -1,33 +0,0 @@ -# Legacy Comparison: ConvertOrientationsFilter - -Date: 2026-06-30 - -## Test Case -Single toy orientation (Euler `(0.7853982, 0.5235988, 1.0471976)` rad = 45°/30°/60°), converted from Euler to each of the 6 other **shared** representations (Orientation Matrix, Quaternion, Axis-Angle, Rodrigues, Homochoric, Cubochoric). Stereographic excluded — no 6.5.171 equivalent (deviation D3). - -## Input Data -The legacy 6.5.171 pipeline (`pipelines/legacy_6_5_171.json`) builds the EulerAngles array from three constant scalars + `CombineAttributeArrays` and writes `results/legacy_out.dream3d`. The NX pipeline (`pipelines/nx.d3dpipeline`) **reads that same file** (NX reads legacy `.dream3d`), so the Euler input is byte-identical (verified: `np.array_equal == True`). - -## Runners -- Legacy: `/Users/mjackson/Workspace9/6.5.172/DREAM3D-Build/D3D-Rel-Qt515-6_5_171/Bin/PipelineRunner` (PipelineRunner 1.2.832, official 6.5.171) -- NX: `/Users/mjackson/Workspace9/DREAM3D-Build/NX-Com-Qt69-Vtk96-Rel/Bin/nxrunner` (1.7.0), EbsdLib 3.0.0 - -## Results — max |Δ| (legacy vs NX), per conversion - -| eu → | max \|Δ\| | -|---|---| -| Quaternion | 0 (bit-identical) | -| Axis-Angle | 0 (bit-identical) | -| Rodrigues | 0 (bit-identical) | -| Homochoric | 0 (bit-identical) | -| Orientation Matrix | 1.49e-08 | -| Cubochoric | 1.78e-06 | - -**Overall max |Δ| = 1.78e-06** (worst single component: cubochoric `cu[1]`, legacy `0.04432392` vs NX `0.04432571`). All conversions agree within 1e-5; four are bit-identical. - -## Fixes Applied -None. SIMPLNX is independently verified-correct against the Class 3 / Class 1 / Class 4 oracle; the legacy output is not wrong, so no legacy patch and no NX change. The sub-2e-6 differences are float32 round-off from library-generation drift: both legacy and NX dispatch each pair to the same direct pairwise transform (e.g. `eu2om`, `eu2cu`), but legacy links OrientationLib while NX links EbsdLib 3.x, and the two accumulate rounding differently. Largest in cubochoric, which involves a cube-root + series expansion most sensitive to intermediate precision. - -## Notes -- Confirms deviation **ConvertOrientationsFilter-D1** (order of operations + library): differences ≤ ~1.8e-6, recommendation "either acceptable within tolerance ~1e-5". -- D2 (float64 scope) not exercised here (input is float32). D3 (Stereographic) has no legacy equivalent. D4 (error codes) is a preflight-only difference. From 879b724c25a8febc4d82418ed61acf2e39a4e6e5 Mon Sep 17 00:00:00 2001 From: Michael Jackson Date: Wed, 22 Jul 2026 21:06:00 -0400 Subject: [PATCH 3/9] TEST: Add mixed hex/non-hex phase test for ComputeCAxisLocations * Exercises the computed c-axis path and the non-hexagonal NaN path in a single execution using the 15 Class 1 Oracle quaternions with phases alternating Hexagonal_High / Cubic_High * Verifies hexagonal cells match the hand-derived oracle values, cubic cells are NaN-filled, and warning -3523 is emitted * Same inputs were used for the legacy 6.5.171 A/B comparison documenting the non-hexagonal behavioral deviations Signed-off-by: Michael Jackson --- .../test/ComputeCAxisLocationsTest.cpp | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/src/Plugins/OrientationAnalysis/test/ComputeCAxisLocationsTest.cpp b/src/Plugins/OrientationAnalysis/test/ComputeCAxisLocationsTest.cpp index a5f2d1446e..d7ccd939bb 100644 --- a/src/Plugins/OrientationAnalysis/test/ComputeCAxisLocationsTest.cpp +++ b/src/Plugins/OrientationAnalysis/test/ComputeCAxisLocationsTest.cpp @@ -228,6 +228,75 @@ TEST_CASE("OrientationAnalysis::ComputeCAxisLocationsFilter: Class 1 Oracle", "[ } } +TEST_CASE("OrientationAnalysis::ComputeCAxisLocationsFilter: Class 1 Oracle - Mixed hexagonal and non-hexagonal phases", "[OrientationAnalysis][ComputeCAxisLocationsFilter]") +{ + DataStructure dataStructure; + + const usize size = k_Fixtures.size(); + + Float32Array* quats = UnitTest::CreateTestDataArray(dataStructure, "Quats", {size}, {4}); + + for(usize i = 0; i < size; i++) + { + for(usize j = 0; j < 4; j++) + { + quats->setComponent(i, j, k_Fixtures[i].inputQuat[j]); + } + } + + UInt32Array* crystalStructures = UnitTest::CreateTestDataArray(dataStructure, "CrystalStructures", {3}, {1}); + crystalStructures->setValue(0, ebsdlib::CrystalStructure::UnknownCrystalStructure); + crystalStructures->setValue(1, ebsdlib::CrystalStructure::Hexagonal_High); + crystalStructures->setValue(2, ebsdlib::CrystalStructure::Cubic_High); + + // Alternate hexagonal (phase 1) and cubic (phase 2) cells so a single execution exercises + // both the computed c-axis path and the non-hexagonal NaN path, and verifies the NaN + // branch does not disturb neighboring computed values. + Int32Array* phases = UnitTest::CreateTestDataArray(dataStructure, "Phases", {size}, {1}); + for(usize i = 0; i < size; i++) + { + phases->setValue(i, i % 2 == 0 ? 1 : 2); + } + + DataPath cAxisLocationsPath({"CAxisLocations"}); + + ComputeCAxisLocationsFilter filter; + Arguments args; + + args.insertOrAssign(ComputeCAxisLocationsFilter::k_QuatsArrayPath_Key, std::make_any(std::vector{"Quats"})); + args.insertOrAssign(ComputeCAxisLocationsFilter::k_CellPhasesArrayPath_Key, std::make_any(std::vector{"Phases"})); + args.insertOrAssign(ComputeCAxisLocationsFilter::k_CrystalStructuresArrayPath_Key, std::make_any(std::vector{"CrystalStructures"})); + args.insertOrAssign(ComputeCAxisLocationsFilter::k_CAxisLocationsArrayName_Key, std::make_any(cAxisLocationsPath.getTargetName())); + + auto preflightResult = filter.preflight(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions); + + auto executeResult = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result); + + REQUIRE(ContainsCode(executeResult.result.warnings(), -3523)); + + REQUIRE(dataStructure.containsData(cAxisLocationsPath)); + auto& cAxisLocations = dataStructure.getDataRefAs(cAxisLocationsPath); + for(usize i = 0; i < size; i++) + { + const bool isHexCell = i % 2 == 0; + for(usize j = 0; j < 3; j++) + { + INFO(fmt::format("i = {} | j = {} | phase = {} | input_quat = ({}) | expected_vec = ({})", i, j, phases->getValue(i), fmt::join(k_Fixtures[i].inputQuat, ", "), + fmt::join(k_Fixtures[i].expectedOutput, ", "))); + if(isHexCell) + { + REQUIRE(cAxisLocations.getComponent(i, j) == Approx(k_Fixtures[i].expectedOutput[j])); + } + else + { + REQUIRE(std::isnan(cAxisLocations.getComponent(i, j))); + } + } + } +} + TEST_CASE("OrientationAnalysis::ComputeCAxisLocationsFilter: SIMPL Backwards Compatibility", "[OrientationAnalysis][ComputeCAxisLocationsFilter][BackwardsCompatibility]") { auto app = Application::GetOrCreateInstance(); From 408ab5243dced568235e4ab52c21f5f4645d6266 Mon Sep 17 00:00:00 2001 From: Jared Duffey Date: Thu, 23 Jul 2026 09:58:42 -0400 Subject: [PATCH 4/9] Added deviations Signed-off-by: Jared Duffey --- .../vv/ComputeCAxisLocationsFilter.md | 8 ++- .../deviations/ComputeCAxisLocationsFilter.md | 54 ++++++++++++++++++- 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/src/Plugins/OrientationAnalysis/vv/ComputeCAxisLocationsFilter.md b/src/Plugins/OrientationAnalysis/vv/ComputeCAxisLocationsFilter.md index ef30426762..ff8fb607d6 100644 --- a/src/Plugins/OrientationAnalysis/vv/ComputeCAxisLocationsFilter.md +++ b/src/Plugins/OrientationAnalysis/vv/ComputeCAxisLocationsFilter.md @@ -20,7 +20,7 @@ A scannable dashboard for reviewers. Each row is one sentence to one short parag | Code paths enumerated | 6 of 7 paths exercised - only the filter cancelation path is untested | | Tests today | 5 test cases - 1 test with Class 1 Oracle, 2 error path tests, 1 warning path test, 1 SIMPL json backwards compatibility test | | Exemplar archive | None - removed test using circular oracle data from `caxis_data.tar.gz` | -| Legacy comparison | Run against DREAM3D 6.5.171 with the inline test data for the Class 1 Oracle. Result is bit-identical between 6.5.171 and NX | +| Legacy comparison | Run against DREAM3D 6.5.171 with the inline test data for the Class 1 Oracle. Result is bit-identical between 6.5.171 and NX for hexagonal phases. | | Bug flags | None | | V&V phase | Ready for review | @@ -99,4 +99,8 @@ No new exemplar archive was created for this V&V cycle: the Class 1 oracle is en ## Deviations from DREAM3D 6.5.171 -- No deviations observed. Comparison run on "OrientationAnalysis::ComputeCAxisLocationsFilter: Class 1 Oracle" between NX and 6.5.171 with bit-identical output. +Comparison run on "OrientationAnalysis::ComputeCAxisLocationsFilter: Class 1 Oracle" between NX and 6.5.171 with bit-identical output for hexagonal phases. The legacy comparison used the inline data **"OrientationAnalysis::ComputeCAxisLocationsFilter: Class 1 Oracle"**: These inline quaternion values were encoded into a csv file. This was then read into a 6.5.171 pipeline with the `FindCAxisLocations` filter and wrote out to a DREAM3D file. The output of NX test was also run and written to a file. These outputs were then verified to have no differences by using `h5py` to read both files and compare the values. + +- D1 - non-hexagonal cells: NaN (NX) vs meaningless-but-finite computed value (6.5.171). NX intentional improvement; no legacy fix warranted. +- D2 - no hexagonal phases present: hard error -3522 (NX) vs silent full execution (6.5.171). +- D3 - added warnings -3521 (preflight, unconditional) and -3523 (execute, mixed phases); legacy emits none. diff --git a/src/Plugins/OrientationAnalysis/vv/deviations/ComputeCAxisLocationsFilter.md b/src/Plugins/OrientationAnalysis/vv/deviations/ComputeCAxisLocationsFilter.md index 8bf5a1012c..cfc56f58e2 100644 --- a/src/Plugins/OrientationAnalysis/vv/deviations/ComputeCAxisLocationsFilter.md +++ b/src/Plugins/OrientationAnalysis/vv/deviations/ComputeCAxisLocationsFilter.md @@ -6,6 +6,56 @@ Entries are referenced by stable ID (`ComputeCAxisLocationsFilter-D`) from th --- -## Headline: No deviations observed +## ComputeCAxisLocationsFilter-D1 -The legacy comparison used the inline data **"OrientationAnalysis::ComputeCAxisLocationsFilter: Class 1 Oracle"**: These inline quaternion values were encoded into a csv file. This was then read into a 6.5.171 pipeline with the `FindCAxisLocations` filter and wrote out to a DREAM3D file. The output of NX test was also run and written to a file. These outputs were then verified to have no differences by using `h5py` to read both files and compare the values. +| Field | Value | +|---|---| +| **Deviation ID** | `ComputeCAxisLocationsFilter-D1` | +| **Filter UUID** | `a51c257a-ddc1-499a-9b21-f2d25a19d098` | +| **Status** | active | + +**Symptom:** For non-hexagonal cells, NX places NaN values whereas 6.5.171 places meaningless-but-finite computed values. + +**Root cause:** Algorithmic choice - Intentional improvement to signal to the user the that the values for those phases could not be calculated. + +**Affected users:** Anyone who has non-hexagonal phases in their input to ComputeCAxisLocationsFilter. + +**Recommendation:** Trust SIMPLNX - ComputeCAxisLocationsFilter's calculation is only correct for hexagonal cells. The legacy filter computes meaningless values for non-hexagonal phases. + +--- + +## ComputeCAxisLocationsFilter-D2 + +| Field | Value | +|---|---| +| **Deviation ID** | `ComputeCAxisLocationsFilter-D2` | +| **Filter UUID** | `a51c257a-ddc1-499a-9b21-f2d25a19d098` | +| **Status** | active | + +**Symptom:** When there are no hexagonal phases present, NX emits an error (-3522) whereas 6.5.171 executes. + +**Root cause:** Algorithmic choice - Intentional improvement to prevent running the filter on data where the filter is not valid. + +**Affected users:** Anyone who has no hexagonal phases in their input to ComputeCAxisLocationsFilter. + +**Recommendation:** Trust SIMPLNX - ComputeCAxisLocationsFilter's calculation is only correct for hexagonal cells. If there are no hexagonal cells, then the filter does no actual calculation. + +--- + +## ComputeCAxisLocationsFilter-D3 + +| Field | Value | +|---|---| +| **Deviation ID** | `ComputeCAxisLocationsFilter-D3` | +| **Filter UUID** | `a51c257a-ddc1-499a-9b21-f2d25a19d098` | +| **Status** | active | + +**Symptom:** NX emits an unconditional warning (-3521) in preflight which advises the user to make sure their data has hexagonal phases and emits a warning (-3523) if there are non-hexagonal phases. 6.5.171 does not emit any warning in either case. + +**Root cause:** Algorithmic choice - Intentional improvement to warn the user that the filter's output is only valid for hexagonal phases. + +**Affected users:** Anyone running the filter for the preflight warning, and anyone who has mixed non-hexagonal phases in their input to ComputeCAxisLocationsFilter. + +**Recommendation:** Trust SIMPLNX - ComputeCAxisLocationsFilter's calculation is only correct for hexagonal cells. + +--- From 106ae36f9900c38815edd0de1e6f89487928ba0a Mon Sep 17 00:00:00 2001 From: Jared Duffey Date: Thu, 23 Jul 2026 10:02:53 -0400 Subject: [PATCH 5/9] Minor cleanup Signed-off-by: Jared Duffey --- .../vv/ComputeCAxisLocationsFilter.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Plugins/OrientationAnalysis/vv/ComputeCAxisLocationsFilter.md b/src/Plugins/OrientationAnalysis/vv/ComputeCAxisLocationsFilter.md index ff8fb607d6..f5c2d38a80 100644 --- a/src/Plugins/OrientationAnalysis/vv/ComputeCAxisLocationsFilter.md +++ b/src/Plugins/OrientationAnalysis/vv/ComputeCAxisLocationsFilter.md @@ -15,8 +15,8 @@ A scannable dashboard for reviewers. Each row is one sentence to one short parag | Aspect | Current state | |------------------------|------------------------------------------------------------------------------------------------------------------------------| -| Algorithm Relationship | Port - The EbsdLib, matrix math, and SIMPL APIs have changed but the code is functionally identical. Addition of several error branchs when the crystal structure type is not hexagonal. | -| Oracle (confirmed) | Class 1 (Analytical) - 2 hand derived data fixtures | +| Algorithm Relationship | Port - The EbsdLib, matrix math, and SIMPL APIs have changed but the code is functionally identical. Addition of several error branches when the crystal structure type is not hexagonal. | +| Oracle (confirmed) | Class 1 (Analytical) - 15 hand derived data fixtures | | Code paths enumerated | 6 of 7 paths exercised - only the filter cancelation path is untested | | Tests today | 5 test cases - 1 test with Class 1 Oracle, 2 error path tests, 1 warning path test, 1 SIMPL json backwards compatibility test | | Exemplar archive | None - removed test using circular oracle data from `caxis_data.tar.gz` | @@ -59,7 +59,7 @@ There were no deviations that affect the output found for hexagonal materials. *Class:* **Class 1 (Analytical)** -*Applied:* Handed derived output of C-axis locations from quaternions. The expected outputs agree between DREAM3DNX, DREAM3D 6.5.171, and manual calculations (`v_passive ​= Rᵀv` with z component forced to postive). Includes 15 different orientations about x, y, and z at different angles. The using the previous formula the exact form results were produced and compared against DREAM3D output. +*Applied:* Handed derived output of C-axis locations from quaternions. The expected outputs agree between DREAM3DNX, DREAM3D 6.5.171, and manual calculations (`v_passive ​= Rᵀv` with z component forced to positive). Includes 15 different orientations about x, y, and z at different angles. Using the previous formula, the exact form results were produced and compared against DREAM3D output. *Encoded:* *`test/ComputeCAxisLocationsTest.cpp::"OrientationAnalysis::ComputeCAxisLocationsFilter: Class 1 Oracle"` - 15 fixtures, all pass.* @@ -69,7 +69,7 @@ There were no deviations that affect the output found for hexagonal materials. *6 of 7 paths exercised. The non-covered path is the cancellation branch which is not currently able to be tested for all filters* -Source: `src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/.cpp` (N lines). +Source: `src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeCAxisLocations.cpp` (107 lines). | # | Phase | Path | Test case | |----|-----------------|---------------------------------------------------|--------------------------------------------| @@ -86,7 +86,7 @@ Source: `src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorit | Test case | Status | Notes | |-----------|--------|-------| | "OrientationAnalysis::ComputeCAxisLocationsFilter: Valid Filter Execution" | retired | Circular oracle | -| "OrientationAnalysis::ComputeCAxisLocationsFilter: InValid Filter Execution" | retired | Superceded by more specific test "OrientationAnalysis::ComputeCAxisLocationsFilter: No hexagonal phases error" | +| "OrientationAnalysis::ComputeCAxisLocationsFilter: InValid Filter Execution" | retired | Superseded by more specific test "OrientationAnalysis::ComputeCAxisLocationsFilter: No hexagonal phases error" | | "OrientationAnalysis::ComputeCAxisLocationsFilter: Preflight Error - Cell array tuple count mismatch (-3520)" | kept | Covers preflight error for tuple mismatch of phases and quats | | "OrientationAnalysis::ComputeCAxisLocationsFilter: SIMPL Backwards Compatibility" | kept | Covers SIMPL json backwards compatibility | | "OrientationAnalysis::ComputeCAxisLocationsFilter: No hexagonal phases error" | new-for-V&V | Covers no hexagonal phases branch | From b72f96d4808b338da675b160f128b0333046b1ba Mon Sep 17 00:00:00 2001 From: Jared Duffey Date: Thu, 23 Jul 2026 10:09:45 -0400 Subject: [PATCH 6/9] Updated filter docs Signed-off-by: Jared Duffey --- .../OrientationAnalysis/docs/ComputeCAxisLocationsFilter.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Plugins/OrientationAnalysis/docs/ComputeCAxisLocationsFilter.md b/src/Plugins/OrientationAnalysis/docs/ComputeCAxisLocationsFilter.md index 9183413de0..c5b3d426c7 100644 --- a/src/Plugins/OrientationAnalysis/docs/ComputeCAxisLocationsFilter.md +++ b/src/Plugins/OrientationAnalysis/docs/ComputeCAxisLocationsFilter.md @@ -8,7 +8,7 @@ Statistics (Crystallography) This **Filter** determines the direction of the C-axis for each **Element** by applying the quaternion of the **Element** to the <001> direction, which is the C-axis for *Hexagonal* materials. This will tell where the C-axis of the **Element** sits in the *sample reference frame*. -*Note:* This **Filter** will only work properly for *Hexagonal* materials. The **Filter** does not apply any symmetry operators because there is only one c-axis (<001>) in *Hexagonal* materials and thus all symmetry operators will leave the c-axis in the same position in the sample *reference frame*. However, in *Cubic* materials, for example, the {100} family of directions are all equivalent and the <001> direction will change location in the *sample reference frame* when symmetry operators are applied. +*Note:* This **Filter** will only work properly for *Hexagonal* materials. The **Filter** does not apply any symmetry operators because there is only one c-axis (<001>) in *Hexagonal* materials and thus all symmetry operators will leave the c-axis in the same position in the sample *reference frame*. However, in *Cubic* materials, for example, the {100} family of directions are all equivalent and the <001> direction will change location in the *sample reference frame* when symmetry operators are applied. **Non-hexagonal** phases will have NAN used for their C-axis location. If there are no hexagonal phases present, the filter will emit an error. % Auto generated parameter table will be inserted here From bd0f0c8f66ab653375a121ed1354b3fbd8b05956 Mon Sep 17 00:00:00 2001 From: Jared Duffey Date: Thu, 23 Jul 2026 10:13:23 -0400 Subject: [PATCH 7/9] Minor test conventions Signed-off-by: Jared Duffey --- .../test/ComputeCAxisLocationsTest.cpp | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/Plugins/OrientationAnalysis/test/ComputeCAxisLocationsTest.cpp b/src/Plugins/OrientationAnalysis/test/ComputeCAxisLocationsTest.cpp index d7ccd939bb..7ebb9b1ea3 100644 --- a/src/Plugins/OrientationAnalysis/test/ComputeCAxisLocationsTest.cpp +++ b/src/Plugins/OrientationAnalysis/test/ComputeCAxisLocationsTest.cpp @@ -103,10 +103,14 @@ TEST_CASE("OrientationAnalysis::ComputeCAxisLocationsFilter: Preflight Error - C SIMPLNX_RESULT_REQUIRE_INVALID(preflightResult.outputActions); REQUIRE(preflightResult.outputActions.errors().size() == 1); REQUIRE(preflightResult.outputActions.errors()[0].code == -3520); + + UnitTest::CheckArraysInheritTupleDims(dataStructure); } TEST_CASE("OrientationAnalysis::ComputeCAxisLocationsFilter: No hexagonal phases error", "[OrientationAnalysis][ComputeCAxisLocationsFilter]") { + UnitTest::LoadPlugins(); + DataStructure dataStructure; UnitTest::CreateTestDataArray(dataStructure, "Quats", {10}, {4}); UnitTest::CreateTestDataArray(dataStructure, "Phases", {10}, {1}); @@ -131,10 +135,14 @@ TEST_CASE("OrientationAnalysis::ComputeCAxisLocationsFilter: No hexagonal phases SIMPLNX_RESULT_REQUIRE_INVALID(executeResult.result); REQUIRE(ContainsCode(executeResult.result.errors(), -3522)); + + UnitTest::CheckArraysInheritTupleDims(dataStructure); } TEST_CASE("OrientationAnalysis::ComputeCAxisLocationsFilter: Not all hexagonal phases warning", "[OrientationAnalysis][ComputeCAxisLocationsFilter]") { + UnitTest::LoadPlugins(); + DataStructure dataStructure; UnitTest::CreateTestDataArray(dataStructure, "Quats", {10}, {4}); @@ -172,10 +180,14 @@ TEST_CASE("OrientationAnalysis::ComputeCAxisLocationsFilter: Not all hexagonal p REQUIRE(dataStructure.containsData(cAxisLocationsPath)); const auto& cAxisLocations = dataStructure.getDataRefAs(cAxisLocationsPath); REQUIRE(std::all_of(cAxisLocations.cbegin(), cAxisLocations.cend(), [](float32 value) { return std::isnan(value); })); + + UnitTest::CheckArraysInheritTupleDims(dataStructure); } TEST_CASE("OrientationAnalysis::ComputeCAxisLocationsFilter: Class 1 Oracle", "[OrientationAnalysis][ComputeCAxisLocationsFilter]") { + UnitTest::LoadPlugins(); + DataStructure dataStructure; const usize size = k_Fixtures.size(); @@ -223,13 +235,17 @@ TEST_CASE("OrientationAnalysis::ComputeCAxisLocationsFilter: Class 1 Oracle", "[ for(usize j = 0; j < 3; j++) { INFO(fmt::format("i = {} | j = {} | input_quat = ({}) | expected_vec = ({})", i, j, fmt::join(k_Fixtures[i].inputQuat, ", "), fmt::join(k_Fixtures[i].expectedOutput, ", "))); - REQUIRE(cAxisLocations.getComponent(i, j) == Approx(k_Fixtures[i].expectedOutput[j])); + REQUIRE(cAxisLocations.getComponent(i, j) == Approx(k_Fixtures[i].expectedOutput[j]).margin(1e-7f)); } } + + UnitTest::CheckArraysInheritTupleDims(dataStructure); } TEST_CASE("OrientationAnalysis::ComputeCAxisLocationsFilter: Class 1 Oracle - Mixed hexagonal and non-hexagonal phases", "[OrientationAnalysis][ComputeCAxisLocationsFilter]") { + UnitTest::LoadPlugins(); + DataStructure dataStructure; const usize size = k_Fixtures.size(); @@ -287,7 +303,7 @@ TEST_CASE("OrientationAnalysis::ComputeCAxisLocationsFilter: Class 1 Oracle - Mi fmt::join(k_Fixtures[i].expectedOutput, ", "))); if(isHexCell) { - REQUIRE(cAxisLocations.getComponent(i, j) == Approx(k_Fixtures[i].expectedOutput[j])); + REQUIRE(cAxisLocations.getComponent(i, j) == Approx(k_Fixtures[i].expectedOutput[j]).margin(1e-7f)); } else { @@ -295,6 +311,8 @@ TEST_CASE("OrientationAnalysis::ComputeCAxisLocationsFilter: Class 1 Oracle - Mi } } } + + UnitTest::CheckArraysInheritTupleDims(dataStructure); } TEST_CASE("OrientationAnalysis::ComputeCAxisLocationsFilter: SIMPL Backwards Compatibility", "[OrientationAnalysis][ComputeCAxisLocationsFilter][BackwardsCompatibility]") From ab07f2ff4c5a7a4fc92ca73410ce256f03dba983 Mon Sep 17 00:00:00 2001 From: Jared Duffey Date: Thu, 23 Jul 2026 10:49:55 -0400 Subject: [PATCH 8/9] Updated paths section Signed-off-by: Jared Duffey --- .../test/ComputeCAxisLocationsTest.cpp | 3 +++ .../vv/ComputeCAxisLocationsFilter.md | 17 +++++++++-------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/Plugins/OrientationAnalysis/test/ComputeCAxisLocationsTest.cpp b/src/Plugins/OrientationAnalysis/test/ComputeCAxisLocationsTest.cpp index 7ebb9b1ea3..2ce4ed746a 100644 --- a/src/Plugins/OrientationAnalysis/test/ComputeCAxisLocationsTest.cpp +++ b/src/Plugins/OrientationAnalysis/test/ComputeCAxisLocationsTest.cpp @@ -172,6 +172,9 @@ TEST_CASE("OrientationAnalysis::ComputeCAxisLocationsFilter: Not all hexagonal p auto preflightResult = filter.preflight(dataStructure, args); SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions); + // Check for unconditional preflight warning about ensuring nonhexagonal data + REQUIRE(ContainsCode(preflightResult.outputActions.warnings(), -3521)); + auto executeResult = filter.execute(dataStructure, args); SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result); diff --git a/src/Plugins/OrientationAnalysis/vv/ComputeCAxisLocationsFilter.md b/src/Plugins/OrientationAnalysis/vv/ComputeCAxisLocationsFilter.md index f5c2d38a80..e9a8dfb70b 100644 --- a/src/Plugins/OrientationAnalysis/vv/ComputeCAxisLocationsFilter.md +++ b/src/Plugins/OrientationAnalysis/vv/ComputeCAxisLocationsFilter.md @@ -17,7 +17,7 @@ A scannable dashboard for reviewers. Each row is one sentence to one short parag |------------------------|------------------------------------------------------------------------------------------------------------------------------| | Algorithm Relationship | Port - The EbsdLib, matrix math, and SIMPL APIs have changed but the code is functionally identical. Addition of several error branches when the crystal structure type is not hexagonal. | | Oracle (confirmed) | Class 1 (Analytical) - 15 hand derived data fixtures | -| Code paths enumerated | 6 of 7 paths exercised - only the filter cancelation path is untested | +| Code paths enumerated | 7 of 8 paths exercised - only the filter cancelation path is untested | | Tests today | 5 test cases - 1 test with Class 1 Oracle, 2 error path tests, 1 warning path test, 1 SIMPL json backwards compatibility test | | Exemplar archive | None - removed test using circular oracle data from `caxis_data.tar.gz` | | Legacy comparison | Run against DREAM3D 6.5.171 with the inline test data for the Class 1 Oracle. Result is bit-identical between 6.5.171 and NX for hexagonal phases. | @@ -67,19 +67,20 @@ There were no deviations that affect the output found for hexagonal materials. ## Code path coverage -*6 of 7 paths exercised. The non-covered path is the cancellation branch which is not currently able to be tested for all filters* +*7 of 8 paths exercised. The non-covered path is the cancellation branch which is not currently able to be tested for all filters* Source: `src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeCAxisLocations.cpp` (107 lines). | # | Phase | Path | Test case | |----|-----------------|---------------------------------------------------|--------------------------------------------| | 1 | *Preflight* | Tuple validity check (-3520 error code) | "OrientationAnalysis::ComputeCAxisLocationsFilter: Preflight Error - Cell array tuple count mismatch (-3520)" | -| 2 | *Execute* | No hexagonal phases check (-3522 error code) | "OrientationAnalysis::ComputeCAxisLocationsFilter: No hexagonal phases error" | -| 3 | *Execute* | Not all phases hexagonal check (-3523 warning code) | "OrientationAnalysis::ComputeCAxisLocationsFilter: Not all hexagonal phases warning" | -| 4 | *Execute - per-cell* | Should cancel check | Not directly tested - no filter cancellation testing infrastructure | -| 5 | *Execute - per-cell* | Hexagonal C-axis location calculation path | "OrientationAnalysis::ComputeCAxisLocationsFilter: Class 1 Oracle" | -| 6 | *Execute - per-cell* | C-axis direction flip check | "OrientationAnalysis::ComputeCAxisLocationsFilter: Class 1 Oracle" | -| 7 | *Execute - per-cell* | Non-hexagonal NAN branch | "OrientationAnalysis::ComputeCAxisLocationsFilter: Not all hexagonal phases warning" | +| 2 | *Preflight* | Unconditional warning advising user to ensure their data contains hexagonal phases | "OrientationAnalysis::ComputeCAxisLocationsFilter: Not all hexagonal phases warning" | +| 3 | *Execute* | No hexagonal phases check (-3522 error code) | "OrientationAnalysis::ComputeCAxisLocationsFilter: No hexagonal phases error" | +| 4 | *Execute* | Not all phases hexagonal check (-3523 warning code) | "OrientationAnalysis::ComputeCAxisLocationsFilter: Not all hexagonal phases warning" | +| 5 | *Execute - per-cell* | Should cancel check | Not directly tested - no filter cancellation testing infrastructure | +| 6 | *Execute - per-cell* | Hexagonal C-axis location calculation path | "OrientationAnalysis::ComputeCAxisLocationsFilter: Class 1 Oracle" | +| 7 | *Execute - per-cell* | C-axis direction flip check | "OrientationAnalysis::ComputeCAxisLocationsFilter: Class 1 Oracle" | +| 8 | *Execute - per-cell* | Non-hexagonal NAN branch | "OrientationAnalysis::ComputeCAxisLocationsFilter: Not all hexagonal phases warning" | ## Test inventory From 103febc634cf77bbe6edc1aec8c702f3a73512ed Mon Sep 17 00:00:00 2001 From: Jared Duffey Date: Thu, 23 Jul 2026 10:54:02 -0400 Subject: [PATCH 9/9] Updated reference frame notes doc reference Signed-off-by: Jared Duffey --- docs/ReferenceFrameNotes.rst | 245 ------------------ .../vv/ComputeCAxisLocationsFilter.md | 2 +- 2 files changed, 1 insertion(+), 246 deletions(-) delete mode 100644 docs/ReferenceFrameNotes.rst diff --git a/docs/ReferenceFrameNotes.rst b/docs/ReferenceFrameNotes.rst deleted file mode 100644 index 1fd69a84be..0000000000 --- a/docs/ReferenceFrameNotes.rst +++ /dev/null @@ -1,245 +0,0 @@ -:topic: Reference Frame Notes - -.. index:: - triple: DREAM3D-NX; Reference Frame; Notes - -Reference Frame Notes -############################### - - -When dealing with **orientation** data such as Euler angles, Quaternions -and Rodrigues vectors, the user will often need to ensure that the -proper convention is followed to transform data from the **sample** -reference frame to the **crystal** reference frame. DREAM3D-NX makes the -following assumptions about reference frames: - -- Only Passive Rotations are used, i.e., rotations between reference - frames; -- A rotations always transforms the **sample** reference frame to the - **crystal** reference frame. - -DREAM3D-NX can represent orientations in seven different forms: - -+---------------+----+------------------------------------------------+ -| Name | A | Number of Components | -| | bb | | -| | r. | | -+===============+====+================================================+ -| Eulers | e | 3 | -+---------------+----+------------------------------------------------+ -| Rodrigues | r | 4 | -+---------------+----+------------------------------------------------+ -| Orientation | o | 3x3 | -| Matrix | | | -+---------------+----+------------------------------------------------+ -| Quaternion | q | 4 (< x, y, z > w) Note the order of the data. | -| | | Vector-Scalar | -+---------------+----+------------------------------------------------+ -| Axis-Angle | a | 4 (< ax0, ax1, ax2 >, w) | -+---------------+----+------------------------------------------------+ -| Cubochoric | c | 3 | -+---------------+----+------------------------------------------------+ -| Homochoric | h | 3 | -+---------------+----+------------------------------------------------+ - -Master Table of Available Conversions -====================================== - -In many cases, a direct transformation is available from one -representation to another; in some cases, an intermediate representation -is used, e.g., from homochoric (h) to Euler (e), DREAM3D-NX first -transforms to an axis angle pair (a), then to an orientation matrix (o), -and finally to Euler angles. - -+--------------------+-------+--------------------+------------+-----------+------------+------------+------------+---------------+ -| From/To | Euler | Orientation Matrix | Axis Angle | Rodrigues | Quaternion | Homochoric | Cubochoric | Stereographic | -+====================+=======+====================+============+===========+============+============+============+===============+ -| Euler | - | X | X | X | X | a | ah | q | -+--------------------+-------+--------------------+------------+-----------+------------+------------+------------+---------------+ -| Orientation Matrix | X | -- | X | e | X | a | ah | q | -+--------------------+-------+--------------------+------------+-----------+------------+------------+------------+---------------+ -| Axis Angle | o | X | -- | X | X | X | h | q | -+--------------------+-------+--------------------+------------+-----------+------------+------------+------------+---------------+ -| Rodrigues | o | a | X | -- | a | X | h | q | -+--------------------+-------+--------------------+------------+-----------+------------+------------+------------+---------------+ -| Quaternion | X | X | X | X | -- | X | h | q | -+--------------------+-------+--------------------+------------+-----------+------------+------------+------------+---------------+ -| Homochoric | ao | a | X | a | a | -- | X | q | -+--------------------+-------+--------------------+------------+-----------+------------+------------+------------+---------------+ -| Cubochoric | hao | ha | h | ha | ha | X | -- | q | -+--------------------+-------+--------------------+------------+-----------+------------+------------+------------+---------------+ -| Stereographic | a | a | X | X | a | X | hc | -- | -+--------------------+-------+--------------------+------------+-----------+------------+------------+------------+---------------+ - -Filters to Assist the User -============================ - -When importing EBSD data through the various readers (.ang, .ctf files), -there are a number of filters that can assist the user in performing the -necessary transformations. The following is a non-exhaustive list of -filters that will help the user perform various manipulations on the -orientation data. - -**NOTE:** When importing data generated by non-EBSD instruments, it is -very important to know what conventions were used to represent the -rotations/orientations; an incorrect interpretation of these conventions -**will** lead to incorrect results. - -+----------+-------------------------------------------------------------+ -| Filter | Discussion | -+==========+=============================================================+ -| Rotate | This will rotate the Crystal Reference frame. Input | -| Euler | Orientation Data is possibly changed | -| R | | -| eference | | -| Frame | | -+----------+-------------------------------------------------------------+ -| Rotate | This will rotate the actual data into new locations in an | -| Sample | Image Geometry array | -| R | | -| eference | | -| Frame | | -+----------+-------------------------------------------------------------+ -| R | DREAM3D-NX expects Rodrigues vectors to have 4 components, | -| odrigues | where the 4th component is the length of the vector. This | -| C | filter will convert 3 component Rodrigues vectors to the | -| onvertor | internal 4 component convention | -+----------+-------------------------------------------------------------+ -| Generate | This uses an input set of Quaternions and generates the | -| Qu | conjugate quaternions. A copy of the input array is made | -| aternion | and changes are made to that array | -| C | | -| onjugate | | -+----------+-------------------------------------------------------------+ -| Generate | This will generate the transpose of an array of 3x3 | -| Ori | matrices. A copy of the input array is made and changes | -| entation | are made to that array | -| Matrix | | -| T | | -| ranspose | | -+----------+-------------------------------------------------------------+ - -Examples of what can go wrong -================================ - -In this section, we show an IPF generated from data imported from a -LabDCT experiment. The LabDCT input data provided 3 component Rodrigues -vectors which were converted to the 4 component internal format. The -LabDCT program generated the IPF on the left, and DREAM3D-NX generated the -IPF on the right, after application of the Rodrigues vector conversion. -Note that while some grain colors match up, most do not. This is because -the LabDCT output was generated with the convention that a rotation -transforms the **Crystal** reference frame to the **Sample** reference -frame, *which is the opposite of what DREAM3D-NX expects*. - -+----------------------------------+-------------------------------------+ -| LabDCT Generated IPF 001 Colors | DREAM3D-NX Generated IPF 001 Colors | -+==================================+=====================================+ -| |Correctly imported orientation | |Incorrectly imported orientation | -| data| | data| | -+----------------------------------+-------------------------------------+ - -While both transformations (Crystal-to-Sample and Sample-to-Crystal) are -passive rotations (they convert reference frames into one another), the -transformations are each other’s inverse. DREAM3D-NX always expects a -rotation angle to be positive and in the interval [0,pi]; thus, -computing the inverse of a rotation involves changing the sign of the -rotation axis unit vector. Depending on the rotation representation, the -inverse of a rotation can then be implemented by computation of: - -- the negative of the 3-component Rodrigues vector (use the Attribute - Array Calculator filter) [note that this also works for the - homochoric and cubochoric representations, and for the axis vector of - the axis-angle pair] -- the conjugate of the Quaternion (Generate Quaternion Conjugate - filter) -- the transpose of the Orientation Matrix (Generate Orientation Matrix - Transpose filter) - -When the input LabDCT Rodrigues vectors are multiplied by -1 **before** -conversion to the internal 4-component format, then the following IPF is -generated, showing perfect agreement with the original LabDCT IPF map. -This example shows that it is absolutely crucial to know and understand -the conventions that were used to generate orientation data in any type -of diffraction experiment **and** to ensure that the correct conversions -are applied upon loading the data into DREAM3D-NX. - -.. figure:: Images/AlCu-485-Transformed.png - :alt: Correctly imported orientation data to conform to DREAM3D-NX’s assumptions - - Correctly imported orientation data to conform to DREAM3D-NX’s - assumptions - -.. |Correctly imported orientation data| image:: Images/AlCu-485-LabDCT.png -.. |Incorrectly imported orientation data| image:: Images/AlCu-485-WRONG.png - -Crystallographic Information -============================== - -+----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ -| # | Point Group (H–M) | Rotation Point Group | Space Group No(s). | Schoenflies | Crystal system | Laue class | Laue Ops | -+====+===================+======================+====================+===============+================+=============+==================+ -| 1 | 1 | 1 | 1 | C₁ | Triclinic | (\bar{1}) | TriclinicOps | -+----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ -| 2 | (\bar{1}) | 1 | 2 | C(_i) | Triclinic | (\bar{1}) | | -+----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ -| 3 | 2 | 2 | 3–5 | C₂ | Monoclinic | 2/m | | -+----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ -| 4 | m | 1 | 6–9 | C(_s) | Monoclinic | 2/m | | -+----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ -| 5 | 2/m | 2 | 10–15 | C(_{2h}) | Monoclinic | 2/m | MonoclinicOps | -+----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ -| 6 | 222 | 222 | 16–24 | D₂ | Orthorhombic | mmm | | -+----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ -| 7 | mm2 | 2 | 25–46 | C(_{2v}) | Orthorhombic | mmm | | -+----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ -| 8 | mmm | 222 | 47–74 | D(_{2h}) | Orthorhombic | mmm | OrthorhombicOps | -+----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ -| 9 | 4 | 4 | 75–80 | C₄ | Tetragonal | 4/m | | -+----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ -| 10 | (\bar{4}) | 2 | 81–82 | S₄ | Tetragonal | 4/m | | -+----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ -| 11 | 4/m | 4 | 83–88 | C(_{4h}) | Tetragonal | 4/m | TetragonalLowOps | -+----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ -| 12 | 422 | 422 | 89–98 | D₄ | Tetragonal | 4/mmm | | -+----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ -| 13 | 4mm | 4 | 99–110 | C(_{4v}) | Tetragonal | 4/mmm | | -+----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ -| 14 | (\bar{4}2m) | 222 | 111–122 | D(_{2d}) | Tetragonal | 4/mmm | | -+----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ -| 15 | 4/mmm | 422 | 123–142 | D(_{4h}) | Tetragonal | 4/mmm | TetragonalOps | -+----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ -| 16 | 3 | 3 | 143–146 | C₃ | Trigonal | (\bar{3}) | | -+----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ -| 17 | (\bar{3}) | 3 | 147–148 | C(_{3i}) (S₆) | Trigonal | (\bar{3}) | TrigonalLowOps | -+----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ -| 18 | 32 | 32 | 149–155 | D₃ | Trigonal | (\bar{3}m) | | -+----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ -| 19 | 3m | 3 | 156–161 | C(_{3v}) | Trigonal | (\bar{3}m) | | -+----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ -| 20 | (\bar{3}m) | 32 | 162–167 | D(_{3d}) | Trigonal | (\bar{3}m) | TrigonalOps | -+----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ -| 21 | 6 | 6 | 168–173 | C₆ | Hexagonal | 6/m | | -+----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ -| 22 | (\bar{6}) | 3 | 174 | C(_{3h}) | Hexagonal | 6/m | | -+----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ -| 23 | 6/m | 6 | 175–176 | C(_{6h}) | Hexagonal | 6/m | HexagonalLowOps | -+----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ -| 24 | 622 | 622 | 177–182 | D₆ | Hexagonal | 6/mmm | | -+----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ -| 25 | 6mm | 6 | 183–186 | C(_{6v}) | Hexagonal | 6/mmm | | -+----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ -| 26 | (\bar{6}m2) | 32 | 187–190 | D(_{3h}) | Hexagonal | 6/mmm | | -+----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ -| 27 | 6/mmm | 622 | 191–194 | D(_{6h}) | Hexagonal | 6/mmm | HexagonalOps | -+----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ -| 28 | 23 | 23 | 195–199 | T | Cubic | m(\bar{3}) | | -+----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ -| 29 | m(\bar{3}) | 23 | 200–206 | T(_h) | Cubic | m(\bar{3}) | CubicLowOps | -+----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ -| 30 | 432 | 432 | 207–214 | O | Cubic | m(\bar{3})m | | -+----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ -| 31 | (\bar{4}3m) | 23 | 215–220 | T(_d) | Cubic | m(\bar{3})m | | -+----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ -| 32 | m(\bar{3})m | 432 | 221–230 | O(_h) | Cubic | m(\bar{3})m | CubicOps | -+----+-------------------+----------------------+--------------------+---------------+----------------+-------------+------------------+ diff --git a/src/Plugins/OrientationAnalysis/vv/ComputeCAxisLocationsFilter.md b/src/Plugins/OrientationAnalysis/vv/ComputeCAxisLocationsFilter.md index e9a8dfb70b..61c0b8f2f6 100644 --- a/src/Plugins/OrientationAnalysis/vv/ComputeCAxisLocationsFilter.md +++ b/src/Plugins/OrientationAnalysis/vv/ComputeCAxisLocationsFilter.md @@ -30,7 +30,7 @@ For worked instances see `src/Plugins/OrientationAnalysis/vv/BadDataNeighborOrie ComputeCAxisLocationsFilter determines the direction of the C-axis for each element, in the *sample reference frame*, by applying the quaternion of the element to the <001> direction, which is the C-axis for *Hexagonal* materials. -The filter is verified with a Class 1 (Analytical) oracle. The filter uses the quaternion to rotate the C-axis into the sample reference frame. This is done by converting the quaternion to a rotation matrix. Then the transpose of the matrix is used due to DREAM3D conventions (see docs/ReferenceFrameNotes.rst). Due to the transpose the sign of the third element may need to be flipped. A new test was added with handed verified data. +The filter is verified with a Class 1 (Analytical) oracle. The filter uses the quaternion to rotate the C-axis into the sample reference frame. This is done by converting the quaternion to a rotation matrix. Then the transpose of the matrix is used due to DREAM3D conventions (see `wrapping/python/docs/source/Reference_Frame_Notes.md`). Due to the transpose the sign of the third element may need to be flipped. A new test was added with handed verified data. There were no deviations that affect the output found for hexagonal materials.