Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Plugins/OrientationAnalysis/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ set(FilterList
ComputeFeatureNeighborCAxisMisalignmentsFilter
ComputeFeatureReferenceCAxisMisorientationsFilter
ComputeFeatureReferenceMisorientationsFilter
ComputeCAxisMisorientationsFilter
ComputeFZQuaternionsFilter
ComputeGBCDFilter
ComputeGBCDMetricBasedFilter
Expand Down Expand Up @@ -188,6 +189,7 @@ set(filter_algorithms
ComputeFeatureNeighborMisorientations
ComputeFeatureReferenceCAxisMisorientations
ComputeFeatureReferenceMisorientations
ComputeCAxisMisorientations
ComputeFZQuaternions
ComputeGBCD
ComputeGBCDMetricBased
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Compute C-Axis Misorientations

## Group (Subgroup)

Statistics (Crystallography)

## Description

This **Filter** computes the angle between each **Feature**'s average C-axis direction and a user-supplied reference direction. The result is a scalar angle in degrees per **Feature**, indicating how closely the grain's C-axis is aligned with a direction of interest in the sample reference frame — such as a loading axis, rolling direction, or sample normal.

### Relationship to C-Axis Misalignment

This filter differs from [Compute Feature Neighbor C-Axis Misalignments](ComputeFeatureNeighborCAxisMisalignmentsFilter.md), which measures the angle *between neighboring grains*. This filter instead measures the angle *between each grain and a fixed reference direction*, making it useful for global texture analysis.

### Prerequisite

The **Average C-Axes** input must be produced by [Compute Average C-Axis Orientations](ComputeAvgCAxesFilter.md) before running this filter. The reference direction is specified in the sample reference frame.

### Hexagonal Materials Only

Because the C-axis is only well-defined in hexagonal crystal systems, [Compute Average C-Axis Orientations](ComputeAvgCAxesFilter.md) marks non-hexagonal **Features** with NaN. This filter propagates those NaN values directly to the output without additional phase checking.

### Result Range

The C-axis has antipodal symmetry — the [001] and [00-1] directions are crystallographically identical. The output is therefore always in **[0°, 90°]**, where 0° means the C-axis is parallel (or antiparallel) to the reference direction and 90° means it is perpendicular.

## Algorithm

For each **Feature**, the filter computes the absolute dot product of the (already-normalized) average C-axis vector with the normalized reference direction, then takes the arc-cosine and converts to degrees:

```
angle = arccos( |avgCAxis · refDir| ) × (180 / π)
```

**Features** whose average C-axis is NaN (non-hexagonal phases) produce NaN in the output.

% Auto generated parameter table will be inserted here

## Example Pipelines

## License & Copyright

Please see the description file distributed with this **Plugin**

## DREAM3D-NX Help

If you need help, need to file a bug report or want to request a new feature, please head over to the [DREAM3DNX-Issues](https://github.com/BlueQuartzSoftware/DREAM3DNX-Issues/discussions) GitHub site where the community of DREAM3D-NX users can help answer your questions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#include "ComputeCAxisMisorientations.hpp"

#include "simplnx/Common/Constants.hpp"
#include "simplnx/DataStructure/DataArray.hpp"

#include <nonstd/span.hpp>

#include <algorithm>
#include <cmath>
#include <limits>

using namespace nx::core;

namespace
{
constexpr usize k_CAxisComponents = 3;
// Feature arrays are small relative to cell arrays, but we still chunk for OOC correctness.
constexpr usize k_MaxChunkTuples = 65536;
// Minimum acceptable magnitude for the reference direction before normalization.
constexpr float64 k_MinRefDirNorm = 1.0e-10;
} // namespace

// -----------------------------------------------------------------------------
ComputeCAxisMisorientations::ComputeCAxisMisorientations(DataStructure& dataStructure, const IFilter::MessageHandler& mesgHandler, const std::atomic_bool& shouldCancel,
ComputeCAxisMisorientationsInputValues* inputValues)
: m_DataStructure(dataStructure)
, m_InputValues(inputValues)
, m_ShouldCancel(shouldCancel)
, m_MessageHandler(mesgHandler)
{
}

// -----------------------------------------------------------------------------
ComputeCAxisMisorientations::~ComputeCAxisMisorientations() noexcept = default;

// -----------------------------------------------------------------------------
Result<> ComputeCAxisMisorientations::operator()()
{
// Normalize the reference direction once. Both vectors must be unit length for
// arccos(dot) to give the correct angle.
const float64 rx = m_InputValues->ReferenceDir[0];
const float64 ry = m_InputValues->ReferenceDir[1];
const float64 rz = m_InputValues->ReferenceDir[2];
const float64 refNorm = std::sqrt(rx * rx + ry * ry + rz * rz);
if(refNorm < k_MinRefDirNorm)
{
return MakeErrorResult(-77001, "Reference direction has zero magnitude. Provide a non-zero reference direction.");
}
const float64 refX = rx / refNorm;
const float64 refY = ry / refNorm;
const float64 refZ = rz / refNorm;

const auto& avgCAxesStoreRef = m_DataStructure.getDataRefAs<Float32Array>(m_InputValues->AvgCAxesArrayPath).getDataStoreRef();
auto& misorientationsStoreRef = m_DataStructure.getDataRefAs<Float32Array>(m_InputValues->MisorientationArrayPath).getDataStoreRef();

const usize totalFeatures = avgCAxesStoreRef.getNumberOfTuples();
if(totalFeatures == 0)
{
return {};
}

const usize chunkTuples = std::min(k_MaxChunkTuples, totalFeatures);
const auto avgCAxesBuffer = std::make_unique<float32[]>(chunkTuples * k_CAxisComponents);
const auto misorientationsBuffer = std::make_unique<float32[]>(chunkTuples);

m_MessageHandler.sendMessage({IFilter::Message::Type::Info, "Computing C-axis misorientations"});

for(usize tupleOffset = 0; tupleOffset < totalFeatures; tupleOffset += chunkTuples)
{
if(m_ShouldCancel)
{
return {};
}

const usize tupleCount = std::min(chunkTuples, totalFeatures - tupleOffset);

if(Result<> ioResult = avgCAxesStoreRef.copyIntoBuffer(tupleOffset * k_CAxisComponents, nonstd::span<float32>(avgCAxesBuffer.get(), tupleCount * k_CAxisComponents)); ioResult.invalid())
{
return ioResult;
}

for(usize tupleIdx = 0; tupleIdx < tupleCount; tupleIdx++)
{
const usize cAxisOffset = tupleIdx * k_CAxisComponents;
const float32 cx = avgCAxesBuffer[cAxisOffset];
const float32 cy = avgCAxesBuffer[cAxisOffset + 1];
const float32 cz = avgCAxesBuffer[cAxisOffset + 2];

// ComputeAvgCAxes marks non-hexagonal features with NaN on all three components.
if(std::isnan(cx))
{
misorientationsBuffer[tupleIdx] = std::numeric_limits<float32>::quiet_NaN();
continue;
}

// AvgCAxes vectors are already normalized. The c-axis has antipodal symmetry
// ([001] == [00-1] in hexagonal), so take |dot| to keep the result in [0°, 90°].
const float64 dot = std::clamp(std::abs(cx * refX + cy * refY + cz * refZ), 0.0, 1.0);
misorientationsBuffer[tupleIdx] = static_cast<float32>(std::acos(dot) * Constants::k_180OverPiD);
}

if(Result<> ioResult = misorientationsStoreRef.copyFromBuffer(tupleOffset, nonstd::span<const float32>(misorientationsBuffer.get(), tupleCount)); ioResult.invalid())
{
return ioResult;
}
}

m_MessageHandler.sendMessage({IFilter::Message::Type::Info, "C-axis misorientation computation complete"});

return {};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#pragma once

#include "OrientationAnalysis/OrientationAnalysis_export.hpp"

#include "simplnx/DataStructure/DataPath.hpp"
#include "simplnx/DataStructure/DataStructure.hpp"
#include "simplnx/Filter/IFilter.hpp"

#include <vector>

namespace nx::core
{

/**
* @struct ComputeCAxisMisorientationsInputValues
* @brief Identifies c-axis misorientation inputs.
*
* AvgCAxes must be a feature-level float32[3] array produced by
* ComputeAvgCAxes. Non-hexagonal features are encoded as NaN by that
* algorithm; this executor propagates those NaN values to the output.
*/
struct ORIENTATIONANALYSIS_EXPORT ComputeCAxisMisorientationsInputValues
{
std::vector<float32> ReferenceDir;
DataPath AvgCAxesArrayPath;
DataPath MisorientationArrayPath;
};

/**
* @class ComputeCAxisMisorientations
* @brief Computes the angle between each feature's average c-axis and a
* user-supplied reference direction.
*
* The c-axis has antipodal symmetry in hexagonal systems, so the result is
* arccos(|cAxis · refDir|) converted to degrees, always in [0°, 90°].
*
* The executor reads AvgCAxes in bounded chunks for OOC compatibility.
* Non-hexagonal features (NaN in AvgCAxes) produce NaN in the output.
*/
class ORIENTATIONANALYSIS_EXPORT ComputeCAxisMisorientations
{
public:
/**
* @brief Initializes c-axis misorientation computation.
* @param dataStructure Provides the selected arrays.
* @param mesgHandler Supplies progress messages.
* @param shouldCancel Signals cancellation.
* @param inputValues Identifies selected arrays and reference direction.
* @pre dataStructure, mesgHandler, shouldCancel, and inputValues outlive
* this executor.
*/
ComputeCAxisMisorientations(DataStructure& dataStructure, const IFilter::MessageHandler& mesgHandler, const std::atomic_bool& shouldCancel, ComputeCAxisMisorientationsInputValues* inputValues);

/**
* @brief Destroys the c-axis misorientation executor.
*/
~ComputeCAxisMisorientations() noexcept;

ComputeCAxisMisorientations(const ComputeCAxisMisorientations&) = delete;
ComputeCAxisMisorientations(ComputeCAxisMisorientations&&) noexcept = delete;
ComputeCAxisMisorientations& operator=(const ComputeCAxisMisorientations&) = delete;
ComputeCAxisMisorientations& operator=(ComputeCAxisMisorientations&&) noexcept = delete;

/**
* @brief Computes c-axis misorientations.
* @return An error if the reference direction has zero magnitude or bulk I/O
* fails. Cancellation returns success with completed chunks preserved.
*/
Result<> operator()();

private:
DataStructure& m_DataStructure;
const ComputeCAxisMisorientationsInputValues* m_InputValues = nullptr;
const std::atomic_bool& m_ShouldCancel;
const IFilter::MessageHandler& m_MessageHandler;
};

} // namespace nx::core
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
#include "ComputeCAxisMisorientationsFilter.hpp"

#include "OrientationAnalysis/Filters/Algorithms/ComputeCAxisMisorientations.hpp"

#include "simplnx/DataStructure/DataArray.hpp"
#include "simplnx/Filter/Actions/CreateArrayAction.hpp"
#include "simplnx/Parameters/ArraySelectionParameter.hpp"
#include "simplnx/Parameters/DataObjectNameParameter.hpp"
#include "simplnx/Parameters/VectorParameter.hpp"

using namespace nx::core;

namespace
{
// Squared-magnitude threshold below which the reference direction is treated as zero.
constexpr float64 k_MinRefDirSquaredNorm = 1.0e-20;
} // namespace

namespace nx::core
{
//------------------------------------------------------------------------------
std::string ComputeCAxisMisorientationsFilter::name() const
{
return FilterTraits<ComputeCAxisMisorientationsFilter>::name.str();
}

//------------------------------------------------------------------------------
std::string ComputeCAxisMisorientationsFilter::className() const
{
return FilterTraits<ComputeCAxisMisorientationsFilter>::className;
}

//------------------------------------------------------------------------------
Uuid ComputeCAxisMisorientationsFilter::uuid() const
{
return FilterTraits<ComputeCAxisMisorientationsFilter>::uuid;
}

//------------------------------------------------------------------------------
std::string ComputeCAxisMisorientationsFilter::humanName() const
{
return "Compute C-Axis Misorientations";
}

//------------------------------------------------------------------------------
std::vector<std::string> ComputeCAxisMisorientationsFilter::defaultTags() const
{
return {className(), "Statistics", "Crystallography"};
}

//------------------------------------------------------------------------------
Parameters ComputeCAxisMisorientationsFilter::parameters() const
{
Parameters params;

params.insertSeparator(Parameters::Separator{"Input Parameter(s)"});
params.insert(std::make_unique<VectorFloat32Parameter>(k_ReferenceDir_Key, "Reference Direction", "The reference axis with respect to which the C-axis misorientation is computed",
std::vector<float32>{0.0F, 0.0F, 1.0F}, std::vector<std::string>{"X", "Y", "Z"}));

params.insertSeparator(Parameters::Separator{"Input Feature Data"});
params.insert(std::make_unique<ArraySelectionParameter>(k_AvgCAxesArrayPath_Key, "Average C-Axes", "The direction of each Feature's C-axis in the sample reference frame", DataPath{},
ArraySelectionParameter::AllowedTypes{DataType::float32}, ArraySelectionParameter::AllowedComponentShapes{{3}}));

params.insertSeparator(Parameters::Separator{"Output Feature Data"});
params.insert(std::make_unique<DataObjectNameParameter>(
k_MisorientationArrayName_Key, "Misorientation Array Name",
"Name of the output feature-level array storing the misorientation angle (in degrees) between each Feature's C-axis and the reference direction", "ReferenceOrientationMisorientation"));

return params;
}

//------------------------------------------------------------------------------
IFilter::VersionType ComputeCAxisMisorientationsFilter::parametersVersion() const
{
return 1;
}

//------------------------------------------------------------------------------
IFilter::UniquePointer ComputeCAxisMisorientationsFilter::clone() const
{
return std::make_unique<ComputeCAxisMisorientationsFilter>();
}

//------------------------------------------------------------------------------
IFilter::PreflightResult ComputeCAxisMisorientationsFilter::preflightImpl(const DataStructure& dataStructure, const Arguments& filterArgs, const MessageHandler& messageHandler,
const std::atomic_bool& shouldCancel, const ExecutionContext& executionContext) const
{
const auto pAvgCAxesArrayPathValue = filterArgs.value<DataPath>(k_AvgCAxesArrayPath_Key);
const auto pMisorientationArrayNameValue = filterArgs.value<std::string>(k_MisorientationArrayName_Key);

const auto pReferenceDirValue = filterArgs.value<std::vector<float32>>(k_ReferenceDir_Key);

Result<OutputActions> resultOutputActions;
std::vector<PreflightValue> preflightUpdatedValues;

const float64 rx = pReferenceDirValue[0];
const float64 ry = pReferenceDirValue[1];
const float64 rz = pReferenceDirValue[2];
if((rx * rx + ry * ry + rz * rz) < k_MinRefDirSquaredNorm)
{
return {MakeErrorResult<OutputActions>(-77000, "Reference direction has zero magnitude. Provide a non-zero reference direction.")};
}

const ShapeType tupleShape = dataStructure.getDataRefAs<Float32Array>(pAvgCAxesArrayPathValue).getTupleShape();
auto createArrayAction = std::make_unique<CreateArrayAction>(DataType::float32, tupleShape, std::vector<usize>{1}, pAvgCAxesArrayPathValue.replaceName(pMisorientationArrayNameValue));
resultOutputActions.value().appendAction(std::move(createArrayAction));

return {std::move(resultOutputActions), std::move(preflightUpdatedValues)};
}

//------------------------------------------------------------------------------
Result<> ComputeCAxisMisorientationsFilter::executeImpl(DataStructure& dataStructure, const Arguments& filterArgs, const PipelineFilter* pipelineNode, const MessageHandler& messageHandler,
const std::atomic_bool& shouldCancel, const ExecutionContext& executionContext) const
{
ComputeCAxisMisorientationsInputValues inputValues;

inputValues.ReferenceDir = filterArgs.value<std::vector<float32>>(k_ReferenceDir_Key);
inputValues.AvgCAxesArrayPath = filterArgs.value<DataPath>(k_AvgCAxesArrayPath_Key);
inputValues.MisorientationArrayPath = inputValues.AvgCAxesArrayPath.replaceName(filterArgs.value<std::string>(k_MisorientationArrayName_Key));

return ComputeCAxisMisorientations(dataStructure, messageHandler, shouldCancel, &inputValues)();
}
} // namespace nx::core
Loading
Loading