Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,22 @@ Orientation Matrix: Determinant must be +1.0

### Data Conversion Notes

If the angles fall outside of this range the **original** Euler Input data **WILL BE CHANGED** to ensure they are within this range.
This **Filter** does not modify the input array; it creates a new output array holding the converted representation. The input orientation values are used as supplied. (DREAM3D 6.5.171 *performed* an in-place Euler range-normalization step that both mutated the input array and — because it applied `fmod(Phi, pi)` and sign flips that are not rotation-preserving — could change the converted orientation for Euler input outside the standard Bunge ranges. That step is deliberately not performed here; see deviation D5.)

## Algorithm Reference

The transformation equations between all representations follow:

> D. Rowenhorst, A. D. Rollett, G. S. Rohrer, M. Groeber, M. Jackson, P. J. Konijnenberg, and M. De Graef, "Consistent representations of and conversions between 3D rotations," *Modelling and Simulation in Materials Science and Engineering* **23**(8), 083501 (2015). DOI: [10.1088/0965-0393/23/8/083501](https://doi.org/10.1088/0965-0393/23/8/083501)

The conversions themselves are implemented in EbsdLib and are verified against this reference by EbsdLib's own test suite.

## Precision Notes

While every effort has been made to ensure the correctness of each transformation algorithm, certain situations may arise where the initial precision of the input data is not large enough for the algorithm to calculate an answer that is intuitive. The user should be acutely aware of their input data and if their data may cause these situations to occur. Combinations of Euler angles close to 0, 180 and 360 can cause these issues to be hit. For instance an Euler angle of [180, 56, 360] is symmetrically the same as [180, 56, 0] and due to calculation errors and round off errors converting that Euler angle between representations may not give the numerical answer the user was anticipating but will give a symmetrically equivalent angle.

The input array must be of type **float32**. Conversions are performed in float32 precision. (DREAM3D 6.5.171 additionally accepted float64 input arrays; see the V&V deviations for this filter.)

% Auto generated parameter table will be inserted here

## Example Pipelines
Expand Down
Original file line number Diff line number Diff line change
@@ -1,159 +1,38 @@
#include "ConvertOrientations.hpp"

#include "simplnx/DataStructure/DataArray.hpp"
#include "simplnx/DataStructure/DataGroup.hpp"
#include "simplnx/Utilities/ParallelDataAlgorithm.hpp"

#include <EbsdLib/Core/Orientation.hpp>
#include <EbsdLib/Math/EbsdLibMath.h>
#include <EbsdLib/Orientation/AxisAngle.hpp>
#include <EbsdLib/Orientation/OrientationFwd.hpp>
#include <EbsdLib/Orientation/Quaternion.hpp>
#include <EbsdLib/Utilities/EbsdStringUtils.hpp>

#include <iostream>
#include <string>
#include <fmt/format.h>

#ifndef _MSC_VER
#pragma clang diagnostic push
#pragma ide diagnostic ignored "UnusedValue"
#endif
#include <array>
#include <string>

using namespace nx::core;

namespace
{

template <typename T>
struct EulerCheck
{

void operator()(T* euler) const
{
euler[0] = static_cast<T>(std::fmod(euler[0], ebsdlib::constants::k_2PiD));
euler[1] = static_cast<T>(std::fmod(euler[1], ebsdlib::constants::k_PiD));
euler[2] = static_cast<T>(std::fmod(euler[2], ebsdlib::constants::k_2PiD));

if(euler[0] < 0.0)
{
euler[0] *= static_cast<T>(-1.0);
}
if(euler[1] < 0.0)
{
euler[1] *= static_cast<T>(-1.0);
}
if(euler[2] < 0.0)
{
euler[2] *= static_cast<T>(-1.0);
}
}
};

template <typename T>
struct OrientationMatrixCheck
{
using OrientationType = ebsdlib::OrientationMatrix<T>;
using ResultType = ebsdlib::ResultType;

void operator()(T* inPtr) const
{
OrientationType oaType(inPtr);

ResultType res = oaType.isValid();
if(res.result <= 0)
{
std::cout << res.msg << std::endl;
printRepresentation(std::cout, inPtr, std::string("Bad OM"));
}
}
void printRepresentation(std::ostream& out, T* om, const std::string& label = std::string("Om")) const
{
out.precision(16);
out << label << om[0] << '\t' << om[1] << '\t' << om[2] << std::endl;
out << label << om[3] << '\t' << om[4] << '\t' << om[5] << std::endl;
out << label << om[6] << '\t' << om[7] << '\t' << om[8] << std::endl;
}
};

template <typename T>
struct QuaternionCheck
{
using OrientationType = ebsdlib::Quaternion<T>;
using ResultType = ebsdlib::ResultType;

void operator()(T* inPtr) const
{
// This is a no-op at this point.
}
};

template <typename T>
struct AxisAngleCheck
{
using OrientationType = ebsdlib::AxisAngle<T>;
using ResultType = ebsdlib::ResultType;

void operator()(T* inPtr) const
{
// This is a no-op at this point.
}
};

template <typename T>
struct RodriguesCheck
{
using OrientationType = ebsdlib::Rodrigues<T>;
using ResultType = ebsdlib::ResultType;

void operator()(T* inPtr) const
{
// This is a no-op at this point.
}
};

template <typename T>
struct HomochoricCheck
{
using OrientationType = ebsdlib::Homochoric<T>;
using ResultType = ebsdlib::ResultType;

void operator()(T* inPtr) const
{
// This is a no-op at this point.
}
};

template <typename T>
struct CubochoricCheck
{
using OrientationType = ebsdlib::Cubochoric<T>;
using ResultType = ebsdlib::ResultType;

void operator()(T* inPtr) const
{
// This is a no-op at this point.
}
};

template <typename T>
struct StereographicCheck
{
using OrientationType = ebsdlib::Stereographic<T>;
using ResultType = ebsdlib::ResultType;

void operator()(T* inPtr) const
{
// This is a no-op at this point.
}
};

// Human-readable representation names, indexed by ebsdlib::orientations::Type.
constexpr std::array<std::string_view, 8> k_TypeNames = {"Euler", "Orientation Matrix", "Quaternion", "Axis Angle", "Rodrigues", "Homochoric", "Cubochoric", "Stereographic"};

// X-macro that generates one per-tuple convertor functor per output representation. Each functor is
// handed to ParallelDataAlgorithm, which splits the tuple range across threads; for tuple i it copies
// the inNumComps input components into an EbsdLib orientation object, calls that object's to<TO_REP>()
// conversion, and writes the outNumComps result components back. Cancel is checked per tuple so a large
// conversion can be aborted promptly. The conversion math itself lives in EbsdLib.
#define OC_TBB_IMPL(TO_REP) \
template <typename T, typename K, class InputType, class OutputType> \
class TO_REP##Convertor \
{ \
public: \
TO_REP##Convertor(ConvertOrientations* filter, nx::core::DataArray<T>& input, nx::core::DataArray<K>& output) \
: m_Input(input.getDataStoreRef()) \
: m_Filter(filter) \
, m_Input(input.getDataStoreRef()) \
, m_Output(output.getDataStoreRef()) \
{ \
} \
Expand All @@ -164,6 +43,10 @@ struct StereographicCheck
size_t outNumComps = m_Output.getNumberOfComponents(); \
for(size_t i = r.min(); i < r.max(); ++i) \
{ \
if(m_Filter->shouldCancel()) \
{ \
return; \
} \
size_t inOffset = i * inNumComps; \
size_t outOffset = i * outNumComps; \
for(size_t c = 0; c < inNumComps; c++) \
Expand All @@ -176,9 +59,11 @@ struct StereographicCheck
m_Output[outOffset + c] = outputInstance[c]; \
} \
} \
m_Filter->sendThreadSafeProgressMessage(r.max() - r.min()); \
} \
\
private: \
ConvertOrientations* m_Filter = nullptr; \
AbstractDataStore<T>& m_Input; \
AbstractDataStore<K>& m_Output; \
};
Expand Down Expand Up @@ -207,27 +92,47 @@ ConvertOrientations::ConvertOrientations(DataStructure& dataStructure, const IFi
ConvertOrientations::~ConvertOrientations() noexcept = default;

// -----------------------------------------------------------------------------
Result<> ConvertOrientations::operator()()
bool ConvertOrientations::shouldCancel() const
{
return m_ShouldCancel;
}

// -----------------------------------------------------------------------------
void ConvertOrientations::sendThreadSafeProgressMessage(usize counter)
{
using ValidateInputDataFunctionType = std::function<void(float32*)>;
std::lock_guard<std::mutex> guard(m_ProgressMessage_Mutex);

m_ProgressCounter += counter;
auto now = std::chrono::steady_clock::now();
if(std::chrono::duration_cast<std::chrono::milliseconds>(now - m_InitialPoint).count() < 1000)
{
return;
}

auto progressInt = static_cast<usize>((static_cast<float32>(m_ProgressCounter) / static_cast<float32>(m_TotalPoints)) * 100.0f);
m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Converting Orientations: {}% Complete", progressInt));
m_InitialPoint = std::chrono::steady_clock::now();
}

// -----------------------------------------------------------------------------
Result<> ConvertOrientations::operator()()
{
DataPath outputDataPath = m_InputValues->InputOrientationArrayPath.replaceName(m_InputValues->OutputOrientationArrayName);
auto inputArray = m_DataStructure.getDataRefAs<Float32Array>(m_InputValues->InputOrientationArrayPath);
auto outputArray = m_DataStructure.getDataRefAs<Float32Array>(outputDataPath);
auto& inputArray = m_DataStructure.getDataRefAs<Float32Array>(m_InputValues->InputOrientationArrayPath);
auto& outputArray = m_DataStructure.getDataRefAs<Float32Array>(outputDataPath);
size_t totalPoints = inputArray.getNumberOfTuples();
m_TotalPoints = totalPoints;

const ValidateInputDataFunctionType euCheck = EulerCheck<float>();
const ValidateInputDataFunctionType omCheck = OrientationMatrixCheck<float>();
const ValidateInputDataFunctionType quCheck = QuaternionCheck<float>();
const ValidateInputDataFunctionType axCheck = AxisAngleCheck<float>();
const ValidateInputDataFunctionType roCheck = RodriguesCheck<float>();
const ValidateInputDataFunctionType hoCheck = HomochoricCheck<float>();
const ValidateInputDataFunctionType cuCheck = CubochoricCheck<float>();
const ValidateInputDataFunctionType stCheck = StereographicCheck<float>();
m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Converting {} orientations from {} to {}", totalPoints, k_TypeNames[static_cast<size_t>(m_InputValues->InputType)],
k_TypeNames[static_cast<size_t>(m_InputValues->OutputType)]));

// Allow data-based parallelization
// Allow data-based parallelization; require both arrays resident in memory for out-of-core stores.
ParallelDataAlgorithm parallelAlgorithm;
parallelAlgorithm.setRange(0, totalPoints);
IParallelAlgorithm::AlgorithmArrays algArrays;
algArrays.push_back(&inputArray);
algArrays.push_back(&outputArray);
parallelAlgorithm.requireArraysInMemory(algArrays);
Comment thread
imikejackson marked this conversation as resolved.

if(m_InputValues->OutputType == ebsdlib::orientations::Type::Euler)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,16 @@

#include <EbsdLib/Orientation/OrientationFwd.hpp>

#include <chrono>
#include <concepts>
#include <mutex>

namespace nx::core
{

namespace convert_orientations_constants
{
// Error Code constants
constexpr int32 k_InputRepresentationTypeError = -67001;
constexpr int32 k_OutputRepresentationTypeError = -67002;
constexpr int32 k_InputComponentDimensionError = -67003;
constexpr int32 k_InputComponentCountError = -67004;
constexpr int32 k_MatchingTypesError = -67005;
Expand Down Expand Up @@ -52,11 +52,30 @@ class ORIENTATIONANALYSIS_EXPORT ConvertOrientations

Result<> operator()();

/**
* @brief Returns true if the user has requested the filter be cancelled. Safe to call from the
* parallel convertor workers.
*/
bool shouldCancel() const;

/**
* @brief Mutex-protected, time-throttled progress reporter. The parallel convertor workers call
* this once per processed chunk; messages are emitted at most ~once per second.
* @param counter Number of tuples completed since the last call.
*/
void sendThreadSafeProgressMessage(usize counter);

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

// Thread safe Progress Message
std::chrono::steady_clock::time_point m_InitialPoint = std::chrono::steady_clock::now();
mutable std::mutex m_ProgressMessage_Mutex;
size_t m_TotalPoints = 0;
size_t m_ProgressCounter = 0;
};

} // namespace nx::core
Loading
Loading