Skip to content

FILT: Add Compute C-Axis Reference Misalignments filter - #1890

Merged
imikejackson merged 9 commits into
BlueQuartzSoftware:developfrom
imikejackson:filt/c_axis_reference_misalignments
Oct 29, 2026
Merged

imikejackson merged 9 commits into
BlueQuartzSoftware:developfrom
imikejackson:filt/c_axis_reference_misalignments

Conversation

@imikejackson

@imikejackson imikejackson commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Summary

This PR adds the Compute C-Axis Reference Misalignments filter. The filter calculates the angle between the hexagonal c-axis of each orientation and a reference direction in the sample frame. Use the angle to find "hard" HCP grains (c-axis nearly parallel to the loading direction) and "soft" HCP grains (c-axis nearly perpendicular to it).

This PR replaces #1761. That PR used a 3-component c-axis array as its input, so the filter also accepted Euler angles and other 3-component arrays and gave incorrect angles. This filter uses quaternions as its input.

  • The input is a quaternion array, a Phases array with the same tuple count, and a Crystal Structures array. The filter works on cell Quats, feature AvgQuats, or other quaternion arrays. The output array is created next to the input array.
  • The filter accepts only Hexagonal_High and Hexagonal_Low phases. Other phases get NaN. A warning gives the number of unmasked tuples with a known non-hexagonal phase. Tuples with UnknownCrystalStructure do not add to this number.
  • An optional mask (bool or uint8) sets NaN on the masked tuples.
  • A zero quaternion or a quaternion with a NaN or infinite component gets NaN. The filter normalizes every other quaternion and moves it to the positive hemisphere before it uses it, so the input quaternions do not need to be unit length.
  • Preflight rejects a reference direction that has a length of 1e-12 or less, or a value that is not finite. Preflight also rejects quaternion and phase arrays, and the mask when it is enabled, that have different tuple counts.
  • The filter reads the arrays in chunks of a fixed size with bulk I/O. It does not allocate temporary arrays of the full size.
  • The output angles are in degrees, from 0 to 90. The c-axis has antipodal symmetry, so the filter uses the absolute value of the dot product.

Test Plan

  • 16 unit tests for the new filter pass in OrientationAnalysisUnitTest (ci-macos-arm64, run serially). The expected values come from the closed-form c-axis for Bunge Euler angles, (sin φ1 sin Φ, −cos φ1 sin Φ, cos Φ), and not from the filter. The tests cover:
    • fixed cases, references that are not unit vectors, and a sweep with a fixed random seed
    • cell and feature levels
    • bool and uint8 masks, and masked non-hexagonal tuples that must not add to the warning
    • mixed phases, chunk boundaries, empty input and cancellation
    • zero, non-finite and scaled quaternions
    • error paths and a rejected Euler-angle input
    • real out-of-core stores (see below)
  • Each test runs with both in-memory store scenarios of the same implementation.
  • The ComputeAvgCAxes, ComputeIPFColors and ComputeCAxisLocations tests still pass (37 of 37 tests in total, in both builds below).
  • A pipeline on a Ti EBSD data set (882,318 cells, 623 features) gives the same angles as an independent Python calculation. The maximum difference is 2.0e-5° for cells and 3.8e-6° for features. The NaN tuples are the same.
  • Out-of-core: the test "out-of-core stores" forces out-of-core storage before it creates the arrays. It uses 12,305 tuples (three full chunks and a partial chunk), bool and uint8 masks, a phase pattern that changes between chunks, and a zero quaternion. In a DREAM3D-NX build with DREAM3DNX_USE_OOC=ON, the test requires that the quaternion, phase, crystal structure, mask and output stores use the HDF5-OOC format. All 37 tests pass in that build and in the ci-macos-arm64 build.
  • The Ti EBSD pipeline in a DREAM3D-NX out-of-core build, with out-of-core storage forced for all arrays, gives output that is identical bit for bit to the in-core output.

Related Issue

Closes #992
Related to #1891

@joeykleingers joeykleingers left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved. I pushed one review commit (fdc9b9977) on top of your filter commit and rebased the branch on develop (705a1b7f2). These are the changes from the review:

Behavior

  • A zero-length quaternion gave 90°, which reads as a "soft" orientation. A quaternion with a non-finite component, or with a length of 1e-6 or less, now gives NaN.
  • The algorithm class now checks its own inputs, as the new code guidelines (#1889) require for a direct call: the type and component shape of each array, the tuple counts of Phases and the mask, and the output array. Before, a 5-component quaternion array was read with the wrong stride and a wrong array type threw.
  • Preflight runs the same input checks. A StringArray selection passes ArraySelectionParameter (#1833, #1821) and made preflight throw std::bad_cast. Now preflight returns an error.
  • Preflight rejects an output name that is the name of an input array.
  • The filter tag is "Statistics", like the other c-axis filters.

Error codes and messages

  • Each failure branch has its own named code (-98710 to -98726). The reference-direction size check no longer shares -98710.
  • The messages give the path, the actual value or type, and an action. The non-hexagonal warning now tells the user how to suppress it.

Tests (15, all pass)

  • Direct algorithm calls for each input check, a StringArray and output-name check in preflight, and scaled, negated, non-finite and near-zero quaternions.
  • Zero tuples and inputs inside a DataGroup.
  • Every error test asserts its code, and tests that share a code also assert the message. The "hexagonal phases only" test now runs through the store scenarios.
  • The tests use float64 and do not call UnitTest::LoadPlugins().

Docs

  • The page names the filters that create the Phases and Crystal Structures inputs and states the NaN rules.

The PR description is updated to match. The out-of-core run in the test plan is still open.

@imikejackson
imikejackson force-pushed the filt/c_axis_reference_misalignments branch from fdc9b99 to 335b259 Compare October 27, 2026 19:47

@joeykleingers joeykleingers left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved on 64e3f64b3. Mike's push replaced my review commit, so I put the review changes back on top of his five commits. Where the changes overlapped, I kept his version:

  • NaN rule: his rule is kept. A zero quaternion or a quaternion with a NaN or infinite component gets NaN. My earlier comment said "a length of 1e-6 or less". That cutoff is not in the code, because it failed his 1e-30 scale test.
  • His tests for masked non-hexagonal tuples, zero, non-finite and scaled quaternions, empty input and cancellation are kept. They now run without UnitTest::LoadPlugins() and through the store scenarios. I removed my tests that his tests duplicated.
  • His header and documentation text is kept. I added the paragraph that names the filters that create the Phases and Crystal Structures inputs.

The review commit also routes all input checks through one ValidateInputs() function. Preflight and the algorithm both call it, and preflight converts its result with ConvertResultTo. The rest of my earlier comment still applies. The 20 ComputeCAxisReferenceMisalignments tests and the 80 CAxis and ComputeIPFColors tests pass.

@imikejackson
imikejackson force-pushed the filt/c_axis_reference_misalignments branch from 64e3f64 to 7c0f915 Compare October 27, 2026 20:41
@imikejackson
imikejackson marked this pull request as draft October 27, 2026 20:51
@imikejackson
imikejackson marked this pull request as ready for review October 27, 2026 21:48
imikejackson and others added 8 commits October 29, 2026 13:42
Add a filter that calculates the angle between the hexagonal c-axis of
each orientation and a reference direction in the sample frame. Use the
angle to find "hard" grains (c-axis near the loading direction) and
"soft" grains (c-axis near perpendicular to it).

* The input is a quaternion array with a Phases array of the same size
  and a Crystal Structures array. The filter works on cell Quats,
  feature AvgQuats, or other quaternion arrays. The output array is
  created next to the input array.
* The filter accepts only Hexagonal_High and Hexagonal_Low phases.
  Other phases and masked tuples get NaN. A warning gives the number of
  tuples with a known non-hexagonal phase. Tuples with
  UnknownCrystalStructure do not add to this number.
* Preflight rejects a reference direction that has zero length or a
  value that is not finite. Preflight also rejects quaternion, phase
  and mask arrays that have different tuple counts.
* The filter reads quaternions, phases and mask values in chunks of a
  fixed size with bulk I/O. It does not allocate temporary arrays of
  the full size.
* The tests compare the results with the closed-form c-axis for Bunge
  Euler angles (sin p1 sin P, -cos p1 sin P, cos P). The tests use
  fixed cases, references that are not unit vectors, and a sweep with
  a fixed random seed. The tests also cover masks, mixed phases, chunk
  boundaries and errors, with both in-memory store scenarios.

Co-authored-by: nyoungbq <nathan.young@bluequartz.net>
Signed-off-by: Michael Jackson <mike.jackson@bluequartz.net>
…Misalignments

* A zero quaternion on an unmasked hexagonal tuple gave a c-axis of
  zero length. The normalized c-axis was not valid, and the filter
  wrote 90 degrees. The filter now writes NaN when the c-axis length is
  zero or not finite.
* Add tests for zero quaternions at chunk boundaries, non-finite
  quaternion components, quaternion scale, empty input and
  cancellation.
* The documentation now states that zero and non-finite quaternions
  get NaN.

Signed-off-by: Michael Jackson <mike.jackson@bluequartz.net>
* Warning -98713 tells users to mask non-hexagonal tuples. No test
  checked that masked tuples with a non-hexagonal phase do not add to
  the warning count. A change that counted them passed all tests.
* Add a test with bool and uint8 masks. All masked non-hexagonal tuples
  give no warning, and one unmasked cubic tuple gives a count of 1.

Signed-off-by: Michael Jackson <mike.jackson@bluequartz.net>
* The class comment now states that zero and non-finite quaternions
  get NaN, and that quaternions need not be unit length.
* The operator() comment now states that the warning counts only
  unmasked tuples with known non-hexagonal structures, and that
  UnknownCrystalStructure tuples do not add to the count.

Signed-off-by: Michael Jackson <mike.jackson@bluequartz.net>
* Rename the members of ComputeCAxisReferenceMisalignmentsInputValues
  to the CamelCase public-member style that .clang-tidy requires and
  that the other C-axis algorithms use.
* The documentation now states that the warning counts only unmasked
  tuples with a known non-hexagonal structure. Split long sentences
  and use "Crystal Structures array" for the ensemble array.
* The input-values comment now names all of its values and states
  which arrays share a tuple count.

Signed-off-by: Michael Jackson <mike.jackson@bluequartz.net>
* Add a test that forces out-of-core storage before it creates the
  arrays. In a DREAM3D-NX out-of-core build, the test requires that the
  quaternion, phase, crystal structure, mask and output stores use the
  HDF5-OOC format. In other builds the format check is skipped.
* The test uses 12,305 tuples (three full chunks and a partial chunk),
  bool and uint8 masks, mixed phases and a zero quaternion. It compares
  the results with the closed-form Euler c-axis and checks the warning
  count.
* The test helpers now write the inputs and read the output with one
  bulk call for each array. Per-element access on out-of-core stores
  made the setup take 91 seconds. The filter itself takes 5 ms.

Signed-off-by: Michael Jackson <mike.jackson@bluequartz.net>
* The out-of-core test set phases with a period of 4. Because 4 divides
  the 4096-tuple chunk size, every chunk had the same phases, and a
  phase chunk read at the wrong offset passed all tests.
* Use a period of 6, so each chunk has a different phase pattern.
  Calculate the expected warning count and the excluded tuples from the
  phase values.

Signed-off-by: Michael Jackson <mike.jackson@bluequartz.net>
…gnments uses it

* Check the quaternion length first. A zero quaternion or a quaternion
  with a NaN or infinite component still gets NaN. The check comes
  before normalize() because normalize() does not check for a zero
  length.
* Normalize the quaternion and move it to the positive hemisphere with
  getPositiveOrientation() before the orientation matrix is made. The
  c-axis check after the matrix is no longer necessary.
* The documentation and the header now state that quaternions are
  normalized before use.

Signed-off-by: Michael Jackson <mike.jackson@bluequartz.net>
@imikejackson
imikejackson force-pushed the filt/c_axis_reference_misalignments branch from e98ab0d to d1cd2f6 Compare October 29, 2026 17:42
The algorithm and preflight call one input check, `ValidateInputs()`, so a direct algorithm call and preflight reject the same invalid arrays without throwing. Each diagnostic has its own named code, and its message names the path, the actual value and an action.

**1. Input validation**

- `Filters/Algorithms/ComputeCAxisReferenceMisalignments.hpp` declares the codes -98710 to -98726 and `ValidateInputs()`.
- `Filters/Algorithms/ComputeCAxisReferenceMisalignments.cpp` implements the checks.
- `Filters/ComputeCAxisReferenceMisalignmentsFilter.cpp` calls it in preflight and converts the result with `ConvertResultTo`.

* `ValidateInputs()` checks the reference direction, the type and component shape of the quaternion, Phases, Crystal Structures and enabled mask arrays, and the tuple count of Phases and the mask. It uses lookups that do not throw.
* The algorithm also checks the output array before it reads data.
* Preflight rejects a StringArray selection with an error instead of `std::bad_cast`.
* Preflight rejects an output name that is the name of an input array (-98726).
* The reference-direction size check has its own code (-98714).
* The filter tag is "Statistics", like the other c-axis filters.

**2. Diagnostics**

* Type errors say whether the path is missing or give its type. Messages quote paths and types and give an action.
* The non-hexagonal warning tells the user how to suppress it.
* The Doxygen blocks use the multi-line layout, and local names follow the naming policy.

**3. Tests**

- `test/ComputeCAxisReferenceMisalignmentsTest.cpp`

* New tests call the algorithm directly for each input check, check StringArray and output-name rejection in preflight, and run with the inputs inside a DataGroup.
* Every error test asserts its code. Tests that share a code also assert the message.
* All tests run through the store scenarios where they execute the filter, use `float64`, and do not call `UnitTest::LoadPlugins()`.

Verification: the 20 ComputeCAxisReferenceMisalignments tests and the 80 CAxis and ComputeIPFColors tests pass in a Release in-core build on macOS arm64. clang-format 19.1.1 reports no changes.

Signed-off-by: Joey Kleingers <joey.kleingers@bluequartz.net>
@imikejackson

Copy link
Copy Markdown
Contributor Author

@joeykleingers I incorporated your suggestions into this PR.

@imikejackson
imikejackson merged commit c4e9d5e into BlueQuartzSoftware:develop Oct 29, 2026
5 checks passed
@imikejackson
imikejackson deleted the filt/c_axis_reference_misalignments branch October 29, 2026 21:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ENH: Compute HCP c-axis angles to a reference direction

2 participants