From 3aeb842f1f0931ddb0d262845f8afa930a25273c Mon Sep 17 00:00:00 2001 From: Michael Jackson Date: Sun, 3 Nov 2024 13:37:47 -0500 Subject: [PATCH] FILT: Add Oxford Channel 5 Binary file reader. - Requires EbsdLib 1.0.34 or higher - Filter requires BOTh .cpr and .crc files to be present - Needs a unit test Signed-off-by: Michael Jackson --- .../OrientationAnalysis/CMakeLists.txt | 2 + .../docs/ReadChannel5DataFilter.md | 67 ++++ .../docs/ReadCtfDataFilter.md | 2 +- .../Filters/Algorithms/ReadChannel5Data.cpp | 177 +++++++++++ .../Filters/Algorithms/ReadChannel5Data.hpp | 97 ++++++ .../Filters/ReadChannel5DataFilter.cpp | 288 ++++++++++++++++++ .../Filters/ReadChannel5DataFilter.hpp | 117 +++++++ .../OrientationAnalysis/test/CMakeLists.txt | 1 + .../test/ReadChannel5DataTest.cpp | 19 ++ 9 files changed, 769 insertions(+), 1 deletion(-) create mode 100644 src/Plugins/OrientationAnalysis/docs/ReadChannel5DataFilter.md create mode 100644 src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ReadChannel5Data.cpp create mode 100644 src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ReadChannel5Data.hpp create mode 100644 src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/ReadChannel5DataFilter.cpp create mode 100644 src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/ReadChannel5DataFilter.hpp create mode 100644 src/Plugins/OrientationAnalysis/test/ReadChannel5DataTest.cpp diff --git a/src/Plugins/OrientationAnalysis/CMakeLists.txt b/src/Plugins/OrientationAnalysis/CMakeLists.txt index 381e7aced8..adf68735bf 100644 --- a/src/Plugins/OrientationAnalysis/CMakeLists.txt +++ b/src/Plugins/OrientationAnalysis/CMakeLists.txt @@ -62,6 +62,7 @@ set(FilterList MergeTwinsFilter NeighborOrientationCorrelationFilter ReadAngDataFilter + ReadChannel5DataFilter ReadCtfDataFilter ReadEnsembleInfoFilter ReadGrainMapper3DFilter @@ -194,6 +195,7 @@ set(filter_algorithms MergeTwins NeighborOrientationCorrelation ReadAngData + ReadChannel5Data ReadCtfData ReadEnsembleInfo ReadGrainMapper3D diff --git a/src/Plugins/OrientationAnalysis/docs/ReadChannel5DataFilter.md b/src/Plugins/OrientationAnalysis/docs/ReadChannel5DataFilter.md new file mode 100644 index 0000000000..669bdc1fe5 --- /dev/null +++ b/src/Plugins/OrientationAnalysis/docs/ReadChannel5DataFilter.md @@ -0,0 +1,67 @@ +# REad Oxford Channel 5 Data File (.cpr/.crc) + +## Group (Subgroup) + +IO (Input) + +## Description + +This filter will read a single .cpr/.crc file pair into a new ImageGeometry. A **Cell Attribute Matrix** and +Ensemble Attribute Matrix** will also be created to hold the imported EBSD information. Currently, the user has no +control over the names of the created **Attribute Arrays**. The user should be aware that simply reading the file +then performing operations that are dependent on the proper crystallographic and sample reference frame will be +**undefined, inaccurate and/or wrong**. In order to bring the crystal reference frame and sample reference frame +into coincidence, rotations will need to be applied to the data. An excellent reference for this is the following PDF file: +[http://pajarito.materials.cmu.edu/rollett/27750/L17-EBSD-analysis-31Mar16.pdf](http://pajarito.materials.cmu.edu/rollett/27750/L17-EBSD-analysis-31Mar16.pdf) + +### Default HKL Transformations + +If the data has come from a HKL acquisition system and the settings of the acquisition software were in the +default modes, then the following reference frame transformations need to be performed: + ++ Sample Reference Frame: 180o about the <010> Axis ++ Crystal Reference Frame: None + +The user also may want to assign un-indexed pixels to be ignored by flagging them as "bad". The Threshold Objects +**Filter** can be used to define this *mask* by thresholding on values such as *Error* = 0. This will mark all scan points +that have Error=0 as TRUE, which means those scan points are valid for processing. + +### Radians and Degrees + +2D .cpr/.crc files have their angles in **radians**. + +### The Axis Alignment Issue for Hexagonal Symmetry [1] + ++ The issue with hexagonal materials is the alignment of the Cartesian coordinate system used for calculations with the crystal coordinate system (the Bravais lattice). ++ In one convention (e.g. EDAX.TSL), the x-axis, i.e. [1,0,0], is aligned with the crystal a1 axis, i.e. the [2,-1,-1,0] direction. In this case, the y-axis is aligned with the [0,1,-1,0] direction. (Green Axis in Figure 1) ++ In the other convention, (e.g. Oxford Instr, Univ. Metz software), the x-axis, i.e. [1,0,0], is aligned with the crystal [1,0,-1,0] direction. In this case, the y-axis is aligned with the [-1,2,-1,0] direction. (Red Axis in Figure 1) ++ This is important because texture analysis can lead to an ambiguity as to the alignment of [2,-1,-1,0] versus [1,0,-1,0], with apparent **30 Degree** shifts in the data. ++ Caution: it appears that the axis alignment is a choice that must be made when installing TSL software so determination of which convention is in use must be made on a case-by-case basis. It is fixed to the y-convention in the HKL software. ++ The main clue that something is wrong in a conversion is that either the 2110 & 1010 pole figures are transposed, or that a peak in the inverse pole figure that should be present at 2110 has shifted over to 1010. ++ DREAM.3D uses the TSL/EDAX convention. ++ **The result of this is that the filter will by default add 30 degrees to the second Euler Angle (phi2) when reading Oxford Instr (.ctf) files. This can be disabled by the user if necessary.** + +| Figure 1 | +|-----------------------------------------------------------------------------------------------------------| +| ![Figure showing 30 Degree conversions](Images/Hexagonal_Axis_Alignment.png) | +| Figure 1:**showing TSL and Oxford Instr. conventions. EDAX/TSL is in **Green**. Oxford Inst. is in**Red | + + +% Auto generated parameter table will be inserted here + + +## Example Pipelines + + + +## License & Copyright + +Please see the description file distributed with this **Plugin** + +## References + +[1] Rollett, A.D. Lecture Slides located at [http://pajarito.materials.cmu.edu/rollett/27750/L17-EBSD-analysis-31Mar16.pdf](http://pajarito.materials.cmu.edu/rollett/27750/L17-EBSD-analysis-31Mar16.pdf) + +## 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. \ No newline at end of file diff --git a/src/Plugins/OrientationAnalysis/docs/ReadCtfDataFilter.md b/src/Plugins/OrientationAnalysis/docs/ReadCtfDataFilter.md index 1a96ef2642..47b411747d 100644 --- a/src/Plugins/OrientationAnalysis/docs/ReadCtfDataFilter.md +++ b/src/Plugins/OrientationAnalysis/docs/ReadCtfDataFilter.md @@ -6,7 +6,7 @@ IO (Input) ## Description -This **Filter** will read a single .ctf file into a new **Image Geometry**, allowing the immediate use of **Filters** on the data instead of having to generate the intermediate .h5ebsd file. A **Cell Attribute Matrix** and Ensemble Attribute Matrix** will also be created to hold the imported EBSD information. Currently, the user has no control over the names of the created **Attribute Arrays**. The user should be aware that simply reading the file then performing operations that are dependent on the proper crystallographic and sample reference frame will be **undefined, inaccurate and/or wrong**. In order to bring the crystal reference frame and sample reference frame into coincidence, rotations will need to be applied to the data. An excellant reference for this is the following PDF file: +This **Filter** will read a single .ctf file into a new **Image Geometry**, allowing the immediate use of **Filters** on the data instead of having to generate the intermediate .h5ebsd file. A **Cell Attribute Matrix** and Ensemble Attribute Matrix** will also be created to hold the imported EBSD information. Currently, the user has no control over the names of the created **Attribute Arrays**. The user should be aware that simply reading the file then performing operations that are dependent on the proper crystallographic and sample reference frame will be **undefined, inaccurate and/or wrong**. In order to bring the crystal reference frame and sample reference frame into coincidence, rotations will need to be applied to the data. An excellent reference for this is the following PDF file: [http://pajarito.materials.cmu.edu/rollett/27750/L17-EBSD-analysis-31Mar16.pdf](http://pajarito.materials.cmu.edu/rollett/27750/L17-EBSD-analysis-31Mar16.pdf) ### Default HKL Transformations diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ReadChannel5Data.cpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ReadChannel5Data.cpp new file mode 100644 index 0000000000..999e1f9ada --- /dev/null +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ReadChannel5Data.cpp @@ -0,0 +1,177 @@ +#include "ReadChannel5Data.hpp" + +#include "simplnx/Common/RgbColor.hpp" +#include "simplnx/DataStructure/DataArray.hpp" +#include "simplnx/DataStructure/Geometry/ImageGeom.hpp" +#include "simplnx/DataStructure/StringArray.hpp" +#include "simplnx/Utilities/Math/MatrixMath.hpp" +#include "simplnx/Utilities/StringUtilities.hpp" + +#include "EbsdLib/Core/Orientation.hpp" + +using namespace nx::core; + +using FloatVec3Type = std::vector; + +namespace +{ +template +void copyRawData(const ReadChannel5DataInputValues* m_InputValues, size_t numElements, DataStructure& m_DataStructure, CprReader& m_Reader, const std::string& name, DataPath& dataArrayPath) +{ + using ArrayType = DataArray; + auto& dataRef = m_DataStructure.getDataRefAs(dataArrayPath); + auto* dataStorePtr = dataRef.getDataStore(); + + const nonstd::span rawDataPtr(reinterpret_cast(m_Reader.getPointerByName(name)), numElements); + // std::copy(rawDataPtr.begin(), rawDataPtr.end(), dataStorePtr->begin() + offset); + for(size_t idx = 0; idx < numElements; idx++) + { + dataStorePtr->setValue(idx, rawDataPtr[idx]); + } +} + +} // namespace + +// ----------------------------------------------------------------------------- +ReadChannel5Data::ReadChannel5Data(DataStructure& dataStructure, const IFilter::MessageHandler& msgHandler, const std::atomic_bool& shouldCancel, ReadChannel5DataInputValues* inputValues) +: m_DataStructure(dataStructure) +, m_MessageHandler(msgHandler) +, m_ShouldCancel(shouldCancel) +, m_InputValues(inputValues) +{ +} + +// ----------------------------------------------------------------------------- +ReadChannel5Data::~ReadChannel5Data() noexcept = default; + +// ----------------------------------------------------------------------------- +Result<> ReadChannel5Data::operator()() +{ + CprReader reader; + reader.setFileName(m_InputValues->InputFile.string()); + const int32_t err = reader.readFile(); + if(err < 0) + { + return MakeErrorResult(reader.getErrorCode(), reader.getErrorMessage()); + } + + const auto result = loadMaterialInfo(&reader); + if(result.first < 0) + { + return MakeErrorResult(result.first, result.second); + } + + copyRawEbsdData(&reader); + + return {}; +} + +// ----------------------------------------------------------------------------- +std::pair ReadChannel5Data::loadMaterialInfo(CprReader* reader) const +{ + const std::vector phases = reader->getPhaseVector(); + if(phases.empty()) + { + return {reader->getErrorCode(), reader->getErrorMessage()}; + } + + const DataPath cellEnsembleAttributeMatrixPath = m_InputValues->DataContainerName.createChildPath(m_InputValues->CellEnsembleAttributeMatrixName); + + auto& crystalStructures = m_DataStructure.getDataRefAs(cellEnsembleAttributeMatrixPath.createChildPath(EbsdLib::CtfFile::CrystalStructures)); + + auto& materialNames = m_DataStructure.getDataRefAs(cellEnsembleAttributeMatrixPath.createChildPath(EbsdLib::CtfFile::MaterialName)); + + auto& latticeConstants = m_DataStructure.getDataRefAs(cellEnsembleAttributeMatrixPath.createChildPath(EbsdLib::CtfFile::LatticeConstants)); + + const std::string invalidPhase = "Invalid Phase"; + + // Initialize the zero'th element to unknowns. The other elements will + // be filled in based on values from the data file + crystalStructures[0] = EbsdLib::CrystalStructure::UnknownCrystalStructure; + materialNames[0] = invalidPhase; + + for(size_t i = 0; i < 6; i++) + { + latticeConstants.getDataStoreRef().setComponent(0, i, 0.0F); + } + + for(const CtfPhase::Pointer& phase : phases) + { + const int32_t phaseID = phase->getPhaseIndex(); + crystalStructures[phaseID] = phase->determineOrientationOpsIndex(); + std::string materialName = phase->getMaterialName(); + materialName = nx::core::StringUtilities::replace(materialName, "MaterialName", ""); + materialName = nx::core::StringUtilities::trimmed(materialName); + materialNames[phaseID] = materialName; + + std::vector lattConst = phase->getLatticeConstants(); + + for(size_t i = 0; i < 6; i++) + { + latticeConstants.getDataStoreRef().setComponent(phaseID, i, lattConst[i]); + } + } + return {0, ""}; +} + +// ----------------------------------------------------------------------------- +void ReadChannel5Data::copyRawEbsdData(CprReader* reader) const +{ + const DataPath cellAttributeMatrixPath = m_InputValues->DataContainerName.createChildPath(m_InputValues->CellAttributeMatrixName); + + std::vector cDims = {1}; + + const auto& imageGeom = m_DataStructure.getDataRefAs(m_InputValues->DataContainerName); + const size_t totalCells = imageGeom.getNumberOfCells(); + + // Prepare the Cell Attribute Matrix with the correct number of tuples based on the total Cells being read from the file. + const std::vector tDims = {imageGeom.getNumXCells(), imageGeom.getNumYCells(), imageGeom.getNumZCells()}; + + const std::vector fieldParsers = reader->createFieldParsers(m_InputValues->InputFile.string()); + for(const auto& parser : fieldParsers) + { + const std::string fieldName = parser.FieldDefinition.FieldName; + DataPath dataArrayPath = cellAttributeMatrixPath.createChildPath(fieldName); + + if(parser.FieldDefinition.numericType == EbsdLib::NumericTypes::Type::Int32) + { + copyRawData(m_InputValues, totalCells, m_DataStructure, *reader, fieldName, dataArrayPath); + } + else if(parser.FieldDefinition.numericType == EbsdLib::NumericTypes::Type::Float) + { + copyRawData(m_InputValues, totalCells, m_DataStructure, *reader, fieldName, dataArrayPath); + } + else if(parser.FieldDefinition.numericType == EbsdLib::NumericTypes::Type::UInt8) + { + copyRawData(m_InputValues, totalCells, m_DataStructure, *reader, fieldName, dataArrayPath); + } + } + + // Copy the data from the 'Phase' array into the 'Phases' array + if(m_InputValues->CreateCompatibleArrays) + { + auto& targetArray = m_DataStructure.getDataRefAs(cellAttributeMatrixPath.createChildPath(EbsdLib::CtfFile::Phases)); + auto* phasePtr = reinterpret_cast(reader->getPointerByName(EbsdLib::Ctf::Phase)); + for(size_t i = 0; i < totalCells; i++) + { + targetArray[i] = phasePtr[i]; + } + } + + // Condense the Euler Angles from 3 separate arrays into a single 1x3 array + if(m_InputValues->CreateCompatibleArrays) + { + const auto* fComp0Ptr = reinterpret_cast(reader->getPointerByName(EbsdLib::Ctf::phi1)); + const auto* fComp1Ptr = reinterpret_cast(reader->getPointerByName(EbsdLib::Ctf::Phi)); + const auto* fComp2Ptr = reinterpret_cast(reader->getPointerByName(EbsdLib::Ctf::phi2)); + cDims[0] = 3; + + auto& cellEulerAngles = m_DataStructure.getDataRefAs(cellAttributeMatrixPath.createChildPath(EbsdLib::CtfFile::EulerAngles)); + for(size_t i = 0; i < totalCells; i++) + { + cellEulerAngles[3 * i] = fComp0Ptr[i]; + cellEulerAngles[3 * i + 1] = fComp1Ptr[i]; + cellEulerAngles[3 * i + 2] = fComp2Ptr[i]; + } + } +} diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ReadChannel5Data.hpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ReadChannel5Data.hpp new file mode 100644 index 0000000000..c9ba7451b1 --- /dev/null +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ReadChannel5Data.hpp @@ -0,0 +1,97 @@ +#pragma once + +#include "OrientationAnalysis/OrientationAnalysis_export.hpp" + +#include "simplnx/DataStructure/DataArray.hpp" +#include "simplnx/DataStructure/DataPath.hpp" +#include "simplnx/DataStructure/DataStructure.hpp" +#include "simplnx/DataStructure/IDataArray.hpp" +#include "simplnx/Filter/IFilter.hpp" +#include "simplnx/Parameters/FileSystemPathParameter.hpp" + +#include "EbsdLib/IO/HKL/CprReader.h" + +#include +namespace fs = std::filesystem; + +namespace nx::core +{ + +struct ORIENTATIONANALYSIS_EXPORT ReadChannel5DataInputValues +{ + FileSystemPathParameter::ValueType InputFile; + DataPath DataContainerName; + std::string CellAttributeMatrixName; + std::string CellEnsembleAttributeMatrixName; + bool EdaxHexagonalAlignment; + bool CreateCompatibleArrays; +}; + +struct ORIENTATIONANALYSIS_EXPORT Ang_Private_Data +{ + std::array dims = {0, 0, 0}; + std::array resolution = {0.0F, 0.0F, 0.0F}; + std::array origin = {0.0F, 0.0F, 0.0F}; + std::vector phases; + int32_t units = 0; +}; + +/** + * @brief The ReadChannel5DataPrivate class is a private implementation of the ReadChannel5Data class + */ +class ORIENTATIONANALYSIS_EXPORT ReadChannel5DataPrivate +{ +public: + ReadChannel5DataPrivate() = default; + ~ReadChannel5DataPrivate() = default; + + ReadChannel5DataPrivate(const ReadChannel5DataPrivate&) = delete; // Copy Constructor Not Implemented + ReadChannel5DataPrivate(ReadChannel5DataPrivate&&) = delete; // Move Constructor Not Implemented + ReadChannel5DataPrivate& operator=(const ReadChannel5DataPrivate&) = delete; // Copy Assignment Not Implemented + ReadChannel5DataPrivate& operator=(ReadChannel5DataPrivate&&) = delete; // Move Assignment Not Implemented + + Ang_Private_Data m_Data; + + std::string m_InputFile_Cache; + fs::file_time_type m_TimeStamp_Cache; +}; + +/** + * @class ReadChannel5Data + * @brief This filter will read a single .ang file into a new Image Geometry, allowing the immediate use of Filters on the data instead of having to generate the intermediate + * .h5ebsd file. + */ +class ORIENTATIONANALYSIS_EXPORT ReadChannel5Data +{ +public: + ReadChannel5Data(DataStructure& dataStructure, const IFilter::MessageHandler& msgHandler, const std::atomic_bool& shouldCancel, ReadChannel5DataInputValues* inputValues); + ~ReadChannel5Data() noexcept; + + ReadChannel5Data(const ReadChannel5Data&) = delete; // Copy Constructor Not Implemented + ReadChannel5Data(ReadChannel5Data&&) = delete; // Move Constructor Not Implemented + ReadChannel5Data& operator=(const ReadChannel5Data&) = delete; // Copy Assignment Not Implemented + ReadChannel5Data& operator=(ReadChannel5Data&&) = delete; // Move Assignment Not Implemented + + Result<> operator()(); + +private: + DataStructure& m_DataStructure; + const IFilter::MessageHandler& m_MessageHandler; + const std::atomic_bool& m_ShouldCancel; + const ReadChannel5DataInputValues* m_InputValues = nullptr; + + /** + * @brief + * @param reader + * @return Error code. + */ + std::pair loadMaterialInfo(CprReader* reader) const; + + /** + * @brief + * @param reader + */ + void copyRawEbsdData(CprReader* reader) const; +}; + +} // namespace nx::core diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/ReadChannel5DataFilter.cpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/ReadChannel5DataFilter.cpp new file mode 100644 index 0000000000..6269261f1d --- /dev/null +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/ReadChannel5DataFilter.cpp @@ -0,0 +1,288 @@ +#include "ReadChannel5DataFilter.hpp" + +#include "OrientationAnalysis/Filters/Algorithms/ReadChannel5Data.hpp" + +#include "simplnx/DataStructure/DataPath.hpp" +#include "simplnx/DataStructure/Geometry/ImageGeom.hpp" +#include "simplnx/Filter/Actions/CreateArrayAction.hpp" +#include "simplnx/Filter/Actions/CreateAttributeMatrixAction.hpp" +#include "simplnx/Filter/Actions/CreateImageGeometryAction.hpp" +#include "simplnx/Filter/Actions/CreateStringArrayAction.hpp" +#include "simplnx/Filter/Actions/DeleteDataAction.hpp" +#include "simplnx/Parameters/BoolParameter.hpp" +#include "simplnx/Parameters/DataGroupCreationParameter.hpp" +#include "simplnx/Parameters/DataObjectNameParameter.hpp" +#include "simplnx/Parameters/FileSystemPathParameter.hpp" +#include "simplnx/Utilities/SIMPLConversion.hpp" + +#include "EbsdLib/IO/HKL/CprReader.h" +#include "EbsdLib/IO/HKL/CtfFields.h" +#include "EbsdLib/IO/HKL/CtfPhase.h" + +#include + +namespace fs = std::filesystem; + +using namespace nx::core; + +namespace nx::core +{ +//------------------------------------------------------------------------------ +std::string ReadChannel5DataFilter::name() const +{ + return FilterTraits::name.str(); +} + +//------------------------------------------------------------------------------ +std::string ReadChannel5DataFilter::className() const +{ + return FilterTraits::className; +} + +//------------------------------------------------------------------------------ +Uuid ReadChannel5DataFilter::uuid() const +{ + return FilterTraits::uuid; +} + +//------------------------------------------------------------------------------ +std::string ReadChannel5DataFilter::humanName() const +{ + return "Read Oxford Instr. Channel 5 (.cpr/.crc)"; +} + +//------------------------------------------------------------------------------ +std::vector ReadChannel5DataFilter::defaultTags() const +{ + return {className(), "IO", "Input", "Read", "Import", "Oxford", "cpr", "crc", "Channel 5", "EBSD"}; +} + +//------------------------------------------------------------------------------ +Parameters ReadChannel5DataFilter::parameters() const +{ + Parameters params; + // Create the parameter descriptors that are needed for this filter + params.insertSeparator(Parameters::Separator{"Input Parameter(s)"}); + params.insert(std::make_unique(k_InputFile_Key, "Input File (.cpr)", "The input .cpr file path. The .crc file must also exist.", fs::path("input.cpr"), + FileSystemPathParameter::ExtensionsType{".cpr"}, FileSystemPathParameter::PathType::InputFile)); + params.insert(std::make_unique(k_EdaxHexagonalAlignment_Key, "Convert Hexagonal X-Axis to EDAX Standard", + "Whether or not to convert a Hexagonal phase to the EDAX standard for x-axis alignment", false)); + params.insert(std::make_unique(k_CreateCompatibleArrays_Key, "Convert Data Arrays to be more compatible with DREAM3D", + "Some arrays will be converted from the type they are in the file to more compatible types.", true)); + + params.insertSeparator(Parameters::Separator{"Output Image Geometry"}); + params.insert(std::make_unique(k_CreatedImageGeometryPath_Key, "Image Geometry", "The path to the created Image Geometry", DataPath({ImageGeom::k_TypeName}))); + params.insertSeparator(Parameters::Separator{"Output Cell Attribute Matrix"}); + params.insert(std::make_unique(k_CellAttributeMatrixName_Key, "Cell Attribute Matrix", "The name of the cell data attribute matrix for the created Image Geometry", + ImageGeom::k_CellDataName)); + params.insertSeparator(Parameters::Separator{"Output Ensemble Attribute Matrix"}); + params.insert(std::make_unique(k_CellEnsembleAttributeMatrixName_Key, "Ensemble Attribute Matrix", "The Attribute Matrix where the phase information is stored.", + "Cell Ensemble Data")); + + return params; +} + +//------------------------------------------------------------------------------ +IFilter::VersionType ReadChannel5DataFilter::parametersVersion() const +{ + return 1; +} + +//------------------------------------------------------------------------------ +IFilter::UniquePointer ReadChannel5DataFilter::clone() const +{ + return std::make_unique(); +} + +//------------------------------------------------------------------------------ +IFilter::PreflightResult ReadChannel5DataFilter::preflightImpl(const DataStructure& dataStructure, const Arguments& filterArgs, const MessageHandler& messageHandler, + const std::atomic_bool& shouldCancel) const +{ + auto pInputFileValue = filterArgs.value(k_InputFile_Key); + auto pImageGeometryPath = filterArgs.value(k_CreatedImageGeometryPath_Key); + auto pCellAttributeMatrixNameValue = filterArgs.value(k_CellAttributeMatrixName_Key); + auto pCellEnsembleAttributeMatrixNameValue = filterArgs.value(k_CellEnsembleAttributeMatrixName_Key); + auto pCreateCompatibleTypes = filterArgs.value(k_CreateCompatibleArrays_Key); + + PreflightResult preflightResult; + + // First validate that the matching .crc file exists + fs::path crcPath = pInputFileValue; + crcPath.replace_extension(".crc"); + try + { + if(!std::filesystem::exists(crcPath)) + { + return {MakeErrorResult(-66500, fmt::format("Matching .crc file does not exist. '{}' ", crcPath.string())), {}}; + } + } catch(std::exception& e) + { + return {MakeErrorResult(-66501, fmt::format("Attempting to figure out if '{}' exists threw a std::exception. Please report this to the developers.", crcPath.string())), {}}; + } + + // Read the CPR file to gather the Geometry information and what arrays are present in the file. + CprReader reader; + reader.setFileName(pInputFileValue.string()); + int32_t err = reader.readHeaderOnly(); + if(err < 0) + { + return {MakeErrorResult(reader.getErrorCode(), reader.getErrorMessage())}; + } + + CreateImageGeometryAction::DimensionType imageGeomDims = {static_cast(reader.getXDimension()), static_cast(reader.getYDimension()), static_cast(1)}; + std::vector tupleDims = {imageGeomDims[2], imageGeomDims[1], imageGeomDims[0]}; + + CreateImageGeometryAction::SpacingType spacing = {reader.getXStep(), reader.getYStep(), 1.0F}; + CreateImageGeometryAction::OriginType origin = {0.0F, 0.0F, 0.0F}; + + // These variables should be updated with the latest data generated for each variable during preflight. + // These will be returned through the preflightResult variable to the + // user interface. + std::stringstream ss; + std::array halfRes = {spacing[0] * 0.5F, spacing[1] * 0.5F, spacing[2] * 0.5F}; + + ss << "X Step: " << reader.getXStep() << " Y Step: " << reader.getYStep() << "\n" + << "Num Cols: " << reader.getXCells() << " " + << "Num Rows: " << reader.getYCells() << "\n" + << "Sample Physical Dimensions: " << (reader.getXStep() * reader.getXCells()) << " (W) x " << (reader.getYStep() * reader.getYCells()) << " (H) microns" + << "\n"; + std::string fileInfo = ss.str(); + std::vector preflightUpdatedValues = {{"Cpr File Information", fileInfo}}; + + // Define an Action that makes changes to the DataStructure + auto createImageGeometryAction = std::make_unique(pImageGeometryPath, CreateImageGeometryAction::DimensionType({imageGeomDims[0], imageGeomDims[1], imageGeomDims[2]}), + origin, spacing, pCellAttributeMatrixNameValue, IGeometry::LengthUnit::Micrometer); + + // Assign the createImageGeometryAction to the Result::actions vector via an appendAction + nx::core::Result resultOutputActions; + resultOutputActions.value().appendAction(std::move(createImageGeometryAction)); + + DataPath cellAttributeMatrixPath = pImageGeometryPath.createChildPath(pCellAttributeMatrixNameValue); + std::vector cDims = {1ULL}; + + auto fieldParsers = reader.createFieldParsers(pInputFileValue.string()); + for(const auto& parser : fieldParsers) + { + if(parser.FieldDefinition.FieldName.empty()) + { + std::cout << "Field Name was empty?\n"; + continue; + } + DataPath dataArrayPath = cellAttributeMatrixPath.createChildPath(parser.FieldDefinition.FieldName); + + if(parser.FieldDefinition.numericType == EbsdLib::NumericTypes::Type::Int32) + { + auto action = std::make_unique(nx::core::DataType::int32, tupleDims, cDims, dataArrayPath); + resultOutputActions.value().appendAction(std::move(action)); + } + else if(parser.FieldDefinition.numericType == EbsdLib::NumericTypes::Type::Float) + { + auto action = std::make_unique(nx::core::DataType::float32, tupleDims, cDims, dataArrayPath); + resultOutputActions.value().appendAction(std::move(action)); + } + else if(parser.FieldDefinition.numericType == EbsdLib::NumericTypes::Type::UInt8) + { + auto action = std::make_unique(nx::core::DataType::uint8, tupleDims, cDims, dataArrayPath); + resultOutputActions.value().appendAction(std::move(action)); + } + } + + // // Create the Cell Phases Array + if(pCreateCompatibleTypes) + { + cDims[0] = 1; + DataPath dataArrayPath = cellAttributeMatrixPath.createChildPath(EbsdLib::CtfFile::Phases); + auto action = std::make_unique(nx::core::DataType::int32, tupleDims, cDims, dataArrayPath); + resultOutputActions.value().appendAction(std::move(action)); + + dataArrayPath = cellAttributeMatrixPath.createChildPath(EbsdLib::Ctf::Phase); + ; + auto removeTempArrayAction = std::make_unique(dataArrayPath); + resultOutputActions.value().appendDeferredAction(std::move(removeTempArrayAction)); + } + // + // // Create the Cell Euler Angles Array + if(pCreateCompatibleTypes) + { + cDims[0] = 3; + DataPath dataArrayPath = cellAttributeMatrixPath.createChildPath(EbsdLib::CtfFile::EulerAngles); + auto action = std::make_unique(nx::core::DataType::float32, tupleDims, cDims, dataArrayPath); + resultOutputActions.value().appendAction(std::move(action)); + + dataArrayPath = cellAttributeMatrixPath.createChildPath(EbsdLib::Ctf::phi1); + ; + auto removeTempArrayAction = std::make_unique(dataArrayPath); + resultOutputActions.value().appendDeferredAction(std::move(removeTempArrayAction)); + + dataArrayPath = cellAttributeMatrixPath.createChildPath(EbsdLib::Ctf::Phi); + ; + removeTempArrayAction = std::make_unique(dataArrayPath); + resultOutputActions.value().appendDeferredAction(std::move(removeTempArrayAction)); + + dataArrayPath = cellAttributeMatrixPath.createChildPath(EbsdLib::Ctf::phi2); + ; + removeTempArrayAction = std::make_unique(dataArrayPath); + resultOutputActions.value().appendDeferredAction(std::move(removeTempArrayAction)); + } + + // Create the Ensemble AttributeMatrix + std::vector> angPhases = reader.getPhaseVector(); + tupleDims = {angPhases.size() + 1}; // Always create 1 extra slot for the phases. + DataPath ensembleAttributeMatrixPath = pImageGeometryPath.createChildPath(pCellEnsembleAttributeMatrixNameValue); + { + auto createAttributeMatrixAction = std::make_unique(ensembleAttributeMatrixPath, tupleDims); + resultOutputActions.value().appendAction(std::move(createAttributeMatrixAction)); + } + + // Create the Crystal Structures Array + { + cDims[0] = 1; + DataPath dataArrayPath = ensembleAttributeMatrixPath.createChildPath(EbsdLib::CtfFile::CrystalStructures); + auto action = std::make_unique(nx::core::DataType::uint32, tupleDims, cDims, dataArrayPath); + resultOutputActions.value().appendAction(std::move(action)); + } + // Create the Lattice Constants Array + { + cDims[0] = 6; + DataPath dataArrayPath = ensembleAttributeMatrixPath.createChildPath(EbsdLib::CtfFile::LatticeConstants); + auto action = std::make_unique(nx::core::DataType::float32, tupleDims, cDims, dataArrayPath); + resultOutputActions.value().appendAction(std::move(action)); + } + // Create the Material Names Array + { + DataPath dataArrayPath = ensembleAttributeMatrixPath.createChildPath(EbsdLib::CtfFile::MaterialName); + auto action = std::make_unique(tupleDims, dataArrayPath); + resultOutputActions.value().appendAction(std::move(action)); + } + + // Return both the resultOutputActions and the preflightUpdatedValues via std::move() + return {std::move(resultOutputActions), std::move(preflightUpdatedValues)}; +} + +//------------------------------------------------------------------------------ +Result<> ReadChannel5DataFilter::executeImpl(DataStructure& dataStructure, const Arguments& filterArgs, const PipelineFilter* pipelineNode, const MessageHandler& messageHandler, + const std::atomic_bool& shouldCancel) const +{ + ReadChannel5DataInputValues inputValues; + + inputValues.InputFile = filterArgs.value(k_InputFile_Key); + inputValues.EdaxHexagonalAlignment = filterArgs.value(k_EdaxHexagonalAlignment_Key); + inputValues.DataContainerName = filterArgs.value(k_CreatedImageGeometryPath_Key); + inputValues.CellAttributeMatrixName = filterArgs.value(k_CellAttributeMatrixName_Key); + inputValues.CellEnsembleAttributeMatrixName = filterArgs.value(k_CellEnsembleAttributeMatrixName_Key); + inputValues.CreateCompatibleArrays = filterArgs.value(k_CreateCompatibleArrays_Key); + return ReadChannel5Data(dataStructure, messageHandler, shouldCancel, &inputValues)(); +} + +namespace +{ +namespace SIMPL +{ +} // namespace SIMPL +} // namespace + +Result ReadChannel5DataFilter::FromSIMPLJson(const nlohmann::json& json) +{ + return {}; +} +} // namespace nx::core diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/ReadChannel5DataFilter.hpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/ReadChannel5DataFilter.hpp new file mode 100644 index 0000000000..ac892e9bbe --- /dev/null +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/ReadChannel5DataFilter.hpp @@ -0,0 +1,117 @@ +#pragma once + +#include "OrientationAnalysis/OrientationAnalysis_export.hpp" + +#include "simplnx/Filter/FilterTraits.hpp" +#include "simplnx/Filter/IFilter.hpp" + +namespace nx::core +{ +/** + * @class ReadChannel5DataFilter + * @brief This filter will read a single .ctf file into a new Image Geometry, allowing the immediate use of Filters on the data instead of having to generate the + * intermediate .h5ebsd file. + */ +class ORIENTATIONANALYSIS_EXPORT ReadChannel5DataFilter : public IFilter +{ +public: + ReadChannel5DataFilter() = default; + ~ReadChannel5DataFilter() noexcept override = default; + + ReadChannel5DataFilter(const ReadChannel5DataFilter&) = delete; + ReadChannel5DataFilter(ReadChannel5DataFilter&&) noexcept = delete; + + ReadChannel5DataFilter& operator=(const ReadChannel5DataFilter&) = delete; + ReadChannel5DataFilter& operator=(ReadChannel5DataFilter&&) noexcept = delete; + + // Parameter Keys + static inline constexpr StringLiteral k_InputFile_Key = "input_file"; + static inline constexpr StringLiteral k_EdaxHexagonalAlignment_Key = "edax_hexagonal_alignment"; + static inline constexpr StringLiteral k_CreateCompatibleArrays_Key = "create_compatible_arrays"; + static inline constexpr StringLiteral k_CreatedImageGeometryPath_Key = "output_image_geometry_path"; + static inline constexpr StringLiteral k_CellAttributeMatrixName_Key = "cell_attribute_matrix_name"; + static inline constexpr StringLiteral k_CellEnsembleAttributeMatrixName_Key = "cell_ensemble_attribute_matrix_name"; + + /** + * @brief Reads SIMPL json and converts it simplnx Arguments. + * @param json + * @return Result + */ + static Result FromSIMPLJson(const nlohmann::json& json); + + /** + * @brief Returns the name of the filter. + * @return + */ + std::string name() const override; + + /** + * @brief Returns the C++ classname of this filter. + * @return + */ + std::string className() const override; + + /** + * @brief Returns the uuid of the filter. + * @return + */ + Uuid uuid() const override; + + /** + * @brief Returns the human readable name of the filter. + * @return + */ + std::string humanName() const override; + + /** + * @brief Returns the default tags for this filter. + * @return + */ + std::vector defaultTags() const override; + + /** + * @brief Returns the parameters of the filter (i.e. its inputs) + * @return + */ + Parameters parameters() const override; + + /** + * @brief Returns parameters version integer. + * Initial version should always be 1. + * Should be incremented everytime the parameters change. + * @return VersionType + */ + VersionType parametersVersion() const override; + + /** + * @brief Returns a copy of the filter. + * @return + */ + UniquePointer clone() const override; + +protected: + /** + * @brief Takes in a DataStructure and checks that the filter can be run on it with the given arguments. + * Returns any warnings/errors. Also returns the changes that would be applied to the DataStructure. + * Some parts of the actions may not be completely filled out if all the required information is not available at preflight time. + * @param dataStructure The input DataStructure instance + * @param filterArgs These are the input values for each parameter that is required for the filter + * @param messageHandler The MessageHandler object + * @return Returns a Result object with error or warning values if any of those occurred during execution of this function + */ + PreflightResult preflightImpl(const DataStructure& dataStructure, const Arguments& filterArgs, const MessageHandler& messageHandler, const std::atomic_bool& shouldCancel) const override; + + /** + * @brief Applies the filter's algorithm to the DataStructure with the given arguments. Returns any warnings/errors. + * On failure, there is no guarantee that the DataStructure is in a correct state. + * @param dataStructure The input DataStructure instance + * @param filterArgs These are the input values for each parameter that is required for the filter + * @param messageHandler The MessageHandler object + * @return Returns a Result object with error or warning values if any of those occurred during execution of this function + */ + Result<> executeImpl(DataStructure& dataStructure, const Arguments& filterArgs, const PipelineFilter* pipelineNode, const MessageHandler& messageHandler, + const std::atomic_bool& shouldCancel) const override; +}; +} // namespace nx::core + +SIMPLNX_DEF_FILTER_TRAITS(nx::core, ReadChannel5DataFilter, "6651923c-afb9-4032-8372-5578325c69a4"); diff --git a/src/Plugins/OrientationAnalysis/test/CMakeLists.txt b/src/Plugins/OrientationAnalysis/test/CMakeLists.txt index cd903dceef..d05eb3df99 100644 --- a/src/Plugins/OrientationAnalysis/test/CMakeLists.txt +++ b/src/Plugins/OrientationAnalysis/test/CMakeLists.txt @@ -41,6 +41,7 @@ set(${PLUGIN_NAME}UnitTest_SRCS MergeTwinsTest.cpp NeighborOrientationCorrelationTest.cpp ReadAngDataTest.cpp + ReadChannel5DataTest.cpp ReadCtfDataTest.cpp ReadEnsembleInfoTest.cpp ReadGrainMapper3DTest.cpp diff --git a/src/Plugins/OrientationAnalysis/test/ReadChannel5DataTest.cpp b/src/Plugins/OrientationAnalysis/test/ReadChannel5DataTest.cpp new file mode 100644 index 0000000000..34c46264b2 --- /dev/null +++ b/src/Plugins/OrientationAnalysis/test/ReadChannel5DataTest.cpp @@ -0,0 +1,19 @@ +#include "OrientationAnalysis/Filters/ReadChannel5DataFilter.hpp" +#include "OrientationAnalysis/OrientationAnalysis_test_dirs.hpp" + +#include "simplnx/Parameters/FileSystemPathParameter.hpp" +#include "simplnx/UnitTest/UnitTestCommon.hpp" + +#include + +#include + +namespace fs = std::filesystem; + +using namespace nx::core; +using namespace nx::core::Constants; +using namespace nx::core::UnitTest; + +TEST_CASE("OrientationAnalysis::ReadChannel5Data: Valid Execution", "[OrientationAnalysis][ReadChannel5Data]") +{ +}