diff --git a/src/Plugins/OrientationAnalysis/CMakeLists.txt b/src/Plugins/OrientationAnalysis/CMakeLists.txt index f2d6825b79..381e7aced8 100644 --- a/src/Plugins/OrientationAnalysis/CMakeLists.txt +++ b/src/Plugins/OrientationAnalysis/CMakeLists.txt @@ -64,6 +64,7 @@ set(FilterList ReadAngDataFilter ReadCtfDataFilter ReadEnsembleInfoFilter + ReadGrainMapper3DFilter ReadH5EbsdFilter ReadH5EspritDataFilter ReadH5OimDataFilter @@ -195,6 +196,7 @@ set(filter_algorithms ReadAngData ReadCtfData ReadEnsembleInfo + ReadGrainMapper3D ReadH5Ebsd ReadH5EspritData ReadH5OimData @@ -246,6 +248,8 @@ set(PLUGIN_EXTRA_SOURCES "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utilities/delaunator.cpp" "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utilities/delaunator.h" "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utilities/IntersectionUtilities.hpp" + "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utilities/GrainMapper3DUtilities.hpp" + "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utilities/GrainMapper3DUtilities.cpp" ) target_sources(${PLUGIN_NAME} PRIVATE ${PLUGIN_EXTRA_SOURCES}) source_group(TREE "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utilities" PREFIX ${PLUGIN_NAME} FILES ${PLUGIN_EXTRA_SOURCES}) diff --git a/src/Plugins/OrientationAnalysis/docs/ReadGrainMapper3DFilter.md b/src/Plugins/OrientationAnalysis/docs/ReadGrainMapper3DFilter.md new file mode 100644 index 0000000000..26aedd8ff6 --- /dev/null +++ b/src/Plugins/OrientationAnalysis/docs/ReadGrainMapper3DFilter.md @@ -0,0 +1,50 @@ +# Read GrainMapper3D File + +## Group (Subgroup) + +Readers + +## Description + +This filter will read Version 4 and Version 5 GrainMapper3D HDF5 files. + +- Euler data is read as radians +- The Image Geometry that is produced is in units of millimeters +- The user has the opportunity to create compatible Orientation Data and Phase data. See below. + +## Parameter Discussion + +GrainMapper3D orientation convention is the same as used by [MTEX](https://mtex-toolbox.github.io), and the inverse of that adapted by DREAM.3D. +This requires certain modifications to the orientation related data (Rodrigues and Quaternions) when being read from the +file. These modifications ensure that when DREAM3D computes orientation related data, the correct results +will be output. + +Specifically, the Rodrigues vector will be converted into a 4 component and the conjugate computed. The quaternion +order will be changed from wxyz to xyzw and the conjugate will be computed. + +PhaseId data will be converted to "int32" (as an option) to make that data immediately compatible +with DREAM3D's filters. + +## Special Notes + +The IPF colors (if any) that are read in from the file are *NOT* compatible with the IPF +Color legends provided by DREAM3D-NX or EBSDLib. The user can use the "Compute IPF Colors" +if they need to specifically understand the crystallographic orientations or they +can obtain the IPF legends from XNovo. + +% Auto generated parameter table will be inserted here + +## References + +[https://xnovotech.com/3d-crystallographic-imaging-software/](https://xnovotech.com/3d-crystallographic-imaging-software/) + +## 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. diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ReadGrainMapper3D.cpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ReadGrainMapper3D.cpp new file mode 100644 index 0000000000..1921c2edea --- /dev/null +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ReadGrainMapper3D.cpp @@ -0,0 +1,285 @@ +#include "ReadGrainMapper3D.hpp" + +#include "OrientationAnalysis/utilities/GrainMapper3DUtilities.hpp" + +#include "simplnx/DataStructure/DataArray.hpp" +#include "simplnx/DataStructure/DataStructure.hpp" +#include "simplnx/DataStructure/Geometry/ImageGeom.hpp" +#include "simplnx/DataStructure/NeighborList.hpp" +#include "simplnx/DataStructure/StringArray.hpp" +#include "simplnx/Filter/IFilter.hpp" +#include "simplnx/Utilities/Parsing/HDF5/H5Support.hpp" +#include "simplnx/Utilities/Parsing/HDF5/Readers/DatasetReader.hpp" + +#include "H5Support/H5Lite.h" +#include "H5Support/H5ScopedSentinel.h" +#include "H5Support/H5Utilities.h" + +#include + +#include +#include + +using namespace nx::core; +using namespace H5Support; +using namespace GrainMapper3DUtilities; +namespace GM3DConst = GrainMapper3DUtilities::Constants; + +namespace +{ + +} // namespace + +namespace EbsdLib::CrystalStructure +{ +inline constexpr uint32_t UnknownCrystalStructure = 999; //!< UnknownCrystalStructure +} + +// ----------------------------------------------------------------------------- +ReadGrainMapper3D::ReadGrainMapper3D(DataStructure& dataStructure, const IFilter::MessageHandler& mesgHandler, const std::atomic_bool& shouldCancel, ReadGrainMapper3DInputValues* inputValues) +: m_DataStructure(dataStructure) +, m_InputValues(inputValues) +, m_ShouldCancel(shouldCancel) +, m_MessageHandler(mesgHandler) +{ +} + +// ----------------------------------------------------------------------------- +const std::atomic_bool& ReadGrainMapper3D::getCancel() +{ + return m_ShouldCancel; +} + +Result<> ReadGrainMapper3D::copyPhaseInformation(GrainMapperReader& reader, hid_t fileId) const +{ + if(!m_InputValues->ReadDctData) + { + return {}; + } + herr_t error = reader.readPhaseInfo(fileId); + if(error < 0) + { + return MakeErrorResult(-39801, fmt::format("Error reading phase info")); + } + + auto phases = reader.getPhaseInformation(); + DataPath cellEnsembleAMPath = m_InputValues->DctImageGeometryPath.createChildPath(m_InputValues->DctCellEnsembleAttributeMatrixName); + + // These arrays are purposely created using the AngFile constant names for BOTH the Oim and the Esprit readers! + auto& crystalStructures = m_DataStructure.getDataRefAs(cellEnsembleAMPath.createChildPath(GM3DConstants::k_CrystalStructures)); + auto& materialNames = m_DataStructure.getDataRefAs(cellEnsembleAMPath.createChildPath(GM3DConstants::k_MaterialName)); + auto& latticeConstantsArray = m_DataStructure.getDataRefAs(cellEnsembleAMPath.createChildPath(GM3DConstants::k_LatticeConstants)); + Float32Array::store_type* latticeConstants = latticeConstantsArray.getDataStore(); + + crystalStructures[0] = EbsdLib::CrystalStructure::UnknownCrystalStructure; + materialNames[0] = "Invalid Phase"; + latticeConstants->setComponent(0, 0, 0.0f); + latticeConstants->setComponent(0, 1, 0.0f); + latticeConstants->setComponent(0, 2, 0.0f); + latticeConstants->setComponent(0, 3, 0.0f); + latticeConstants->setComponent(0, 4, 0.0f); + latticeConstants->setComponent(0, 5, 0.0f); + int32 index = 1; + for(const auto& phase : phases) + { + const int32 phaseId = index++; + crystalStructures[phaseId] = GrainMapper3DUtilities::GetLaueIndexFromSpaceGroup(phase.SpaceGroup); + materialNames[phaseId] = phase.Name; + std::vector lc = phase.UnitCell; + + latticeConstants->setComponent(phaseId, 0, static_cast(lc[0])); + latticeConstants->setComponent(phaseId, 1, static_cast(lc[1])); + latticeConstants->setComponent(phaseId, 2, static_cast(lc[2])); + latticeConstants->setComponent(phaseId, 3, static_cast(lc[3])); + latticeConstants->setComponent(phaseId, 4, static_cast(lc[4])); + latticeConstants->setComponent(phaseId, 5, static_cast(lc[5])); + } + + return {}; +} + +Result<> ReadGrainMapper3D::copyDctData(GrainMapperReader& reader, hid_t fileId) const +{ + if(!m_InputValues->ReadDctData) + { + return {}; + } + + hid_t labDctGid = H5Gopen(fileId, GM3DConst::k_LabDCTGroupName.c_str(), H5P_DEFAULT); + if(labDctGid < 0) + { + return MakeErrorResult(-89300, fmt::format("ReadGrainMapper3D: Error opening '{}' group.", GM3DConst::k_LabDCTGroupName)); + } + auto groupSentinel = H5Support::H5ScopedGroupSentinel(labDctGid, true); + + // Now check that each of the known data sets exist + // Get the Image Geometry Dimensions + hid_t dataGid = H5Gopen(labDctGid, GM3DConst::k_DataGroupName.c_str(), H5P_DEFAULT); + if(dataGid < 0) + { + return MakeErrorResult(-89301, fmt::format("ReadGrainMapper3D: Error opening '/LabDCT/{}' group.", GM3DConst::k_DataGroupName)); + } + groupSentinel.addGroupId(dataGid); + + reader.findAvailableDctDatasets(labDctGid); + auto dctDataSets = reader.getDctDatasetNames(); + + std::vector floatDataSets = {GM3DConst::k_CompletenessName, GM3DConst::k_EulerZXZName, GM3DConst::k_EulerZYZName, GM3DConst::k_QuaternionName, GM3DConst::k_RodriguesName}; + std::vector in32DataSets = {GM3DConst::k_GrainIdName}; + std::vector uint8DataSets = {GM3DConst::k_MaskName, GM3DConst::k_IPF001Name, GM3DConst::k_IPF010Name, GM3DConst::k_IPF100Name, GM3DConst::k_PhaseIdName}; + Result<> result; + + // We need to special case this because we are converting from a uint8 value to an int32 value. + if(m_InputValues->ConvertPhaseData && (std::count(dctDataSets.begin(), dctDataSets.end(), GM3DConst::k_PhaseIdName) > 0)) + { + uint8DataSets.pop_back(); // Pop off the PhaseIdName data set since we are specifically reading it here. + std::vector phaseU8; + herr_t error = H5Lite::readVectorDataset(dataGid, GM3DConst::k_PhaseIdName, phaseU8); + if(error < 0) + { + return MakeErrorResult(-89302, fmt::format("ReadGrainMapper3D: Error reading '/LabDCT/Data/{}' dataset.", GM3DConst::k_PhaseIdName)); + } + DataPath dataArrayPath = m_InputValues->DctImageGeometryPath.createChildPath(m_InputValues->DctCellAttributeMatrixName).createChildPath(GM3DConst::k_PhaseIdName); + + auto& phaseI32 = m_DataStructure.getDataAs(dataArrayPath)->getDataStoreRef(); + // Copy the data from the temp buffer into the final spot. + std::copy(phaseU8.begin(), phaseU8.end(), phaseI32.begin()); + } + + // We need to special case this because we are converting from a 3 component to a 4 component + if(m_InputValues->ConvertOrientationData && (std::count(dctDataSets.begin(), dctDataSets.end(), GM3DConst::k_RodriguesName) > 0)) + { + floatDataSets.pop_back(); // Pop off the Rodrigues data set since we are specifically reading it here. + std::vector gm3dRoData; + herr_t error = H5Lite::readVectorDataset(dataGid, GM3DConst::k_RodriguesName, gm3dRoData); + if(error < 0) + { + return MakeErrorResult(-89303, fmt::format("ReadGrainMapper3D: Error reading '/LabDCT/Data/{}' dataset.", GM3DConst::k_RodriguesName)); + } + DataPath dataArrayPath = m_InputValues->DctImageGeometryPath.createChildPath(m_InputValues->DctCellAttributeMatrixName).createChildPath(GM3DConst::k_RodriguesName); + + auto& rodData = m_DataStructure.getDataAs(dataArrayPath)->getDataStoreRef(); + // Copy the data from the temp buffer into the final spot doing the conversion on the fly + // See the section on reference frames to understand what is going on in here. + for(size_t t = 0; t < rodData.getNumberOfTuples(); t++) + { + const float32 r0 = gm3dRoData[t * 3] * -1.0f; + const float32 r1 = gm3dRoData[t * 3 + 1] * -1.0f; + const float32 r2 = gm3dRoData[t * 3 + 2] * -1.0f; + const float length = sqrtf(r0 * r0 + r1 * r1 + r2 * r2); + + rodData[t * 4] = r0 / length; + rodData[t * 4 + 1] = r1 / length; + rodData[t * 4 + 2] = r2 / length; + rodData[t * 4 + 3] = length; + } + } + + // Read all remaining data sets from the HDF5 file. + for(const auto& dataSetName : dctDataSets) + { + DataPath dataArrayPath = m_InputValues->DctImageGeometryPath.createChildPath(m_InputValues->DctCellAttributeMatrixName).createChildPath(dataSetName); + + nx::core::HDF5::DatasetReader datasetReader(dataGid, dataSetName); + + if(std::count(floatDataSets.begin(), floatDataSets.end(), dataSetName) > 0) + { + result = nx::core::HDF5::Support::FillDataArray(m_DataStructure, dataArrayPath, datasetReader); + } + else if(std::count(in32DataSets.begin(), in32DataSets.end(), dataSetName) > 0) + { + result = nx::core::HDF5::Support::FillDataArray(m_DataStructure, dataArrayPath, datasetReader); + } + else if(std::count(uint8DataSets.begin(), uint8DataSets.end(), dataSetName) > 0) + { + result = nx::core::HDF5::Support::FillDataArray(m_DataStructure, dataArrayPath, datasetReader); + } + if(result.invalid()) + { + return result; + } + } + + // Convert the Quaternions Reference Frame and ordering if asked by the user and if the data set exists + if((std::count(dctDataSets.begin(), dctDataSets.end(), GM3DConst::k_QuaternionName) > 0) && m_InputValues->ConvertOrientationData) + { + DataPath dataArrayPath = m_InputValues->DctImageGeometryPath.createChildPath(m_InputValues->DctCellAttributeMatrixName).createChildPath(GM3DConst::k_QuaternionName); + auto& quatData = m_DataStructure.getDataAs(dataArrayPath)->getDataStoreRef(); + // Copy the data from the temp buffer into the final spot doing the conversion on the fly + // We are reordering from wxyz (Scalar-Vector) to xyzw (Vetor-Scalar) and at the same time + // we are taking the conjugate of the quaternion + for(size_t t = 0; t < quatData.getNumberOfTuples(); t++) + { + const float32 w = quatData[t * 4]; + const float32 x = quatData[t * 4 + 1] * -1.0f; + const float32 y = quatData[t * 4 + 2] * -1.0f; + const float32 z = quatData[t * 4 + 3] * -1.0f; + + quatData[t * 4] = x; + quatData[t * 4 + 1] = y; + quatData[t * 4 + 2] = z; + quatData[t * 4 + 3] = w; + } + } + return {}; +} + +Result<> ReadGrainMapper3D::copyAbsorptionData(GrainMapperReader& reader, hid_t fileId) const +{ + if(!m_InputValues->ReadAbsorptionData) + { + return {}; + } + hid_t gid = H5Gopen(fileId, GM3DConst::k_AbsorptionCTName.c_str(), H5P_DEFAULT); + if(gid < 0) + { + return MakeErrorResult(-89350, fmt::format("ReadGrainMapper3D: Error opening '{}' group.", GM3DConst::k_AbsorptionCTName)); + } + auto groupSentinel = H5Support::H5ScopedGroupSentinel(gid, true); + + DataPath dataArrayPath = + m_InputValues->AbsorptionImageGeometryPath.createChildPath(m_InputValues->AbsorptionCellAttributeMatrixName).createChildPath(GrainMapper3DUtilities::Constants::k_DataGroupName); + + nx::core::HDF5::DatasetReader datasetReader(gid, GM3DConst::k_DataGroupName); + + return nx::core::HDF5::Support::FillDataArray(m_DataStructure, dataArrayPath, datasetReader); +} + +// ----------------------------------------------------------------------------- +Result<> ReadGrainMapper3D::operator()() +{ + GrainMapperReader reader(m_InputValues->InputFile.string(), m_InputValues->ReadDctData, m_InputValues->ReadAbsorptionData); + + hid_t fileId = H5Support::H5Utilities::openFile(m_InputValues->InputFile.string(), true); + if(fileId < 0) + { + return MakeErrorResult(-89350, fmt::format("Grain Mapper 3D File '{}' could not be opened.", m_InputValues->InputFile.string())); + } + auto sentinel = H5Support::H5ScopedFileSentinel(fileId, false); + + // *********************************************************************** + // Read the Phase Information + Result<> result = copyPhaseInformation(reader, fileId); + if(result.invalid()) + { + return result; + } + + // *********************************************************************** + // Read the LabDCT Information + result = copyDctData(reader, fileId); + if(result.invalid()) + { + return result; + } + + // *********************************************************************** + // Read the LabDCT Information + result = copyAbsorptionData(reader, fileId); + if(result.invalid()) + { + return result; + } + + return {}; +} diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ReadGrainMapper3D.hpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ReadGrainMapper3D.hpp new file mode 100644 index 0000000000..ee571e9fb3 --- /dev/null +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ReadGrainMapper3D.hpp @@ -0,0 +1,70 @@ +#pragma once + +#include "OrientationAnalysis/OrientationAnalysis_export.hpp" +#include "OrientationAnalysis/utilities/GrainMapper3DUtilities.hpp" + +#include "simplnx/DataStructure/DataPath.hpp" +#include "simplnx/DataStructure/DataStructure.hpp" +#include "simplnx/Filter/IFilter.hpp" + +#include + +using namespace GrainMapper3DUtilities; + +namespace nx::core +{ + +struct ORIENTATIONANALYSIS_EXPORT ReadGrainMapper3DInputValues +{ + std::filesystem::path InputFile; + bool ReadDctData; + DataPath DctImageGeometryPath; + std::string DctCellAttributeMatrixName; + std::string DctCellEnsembleAttributeMatrixName; + bool ConvertPhaseData; + bool ConvertOrientationData; + + bool ReadAbsorptionData; + DataPath AbsorptionImageGeometryPath; + std::string AbsorptionCellAttributeMatrixName; +}; + +namespace GM3DConstants +{ +const std::string k_CrystalStructures("CrystalStructures"); +const std::string k_LatticeConstants("LatticeConstants"); +const std::string k_MaterialName("MaterialName"); +} // namespace GM3DConstants +/** + * @class ReadGrainMapper3D + * @brief This filter determines the average C-axis location of each Feature. + */ + +class ORIENTATIONANALYSIS_EXPORT ReadGrainMapper3D +{ +public: + ReadGrainMapper3D(DataStructure& dataStructure, const IFilter::MessageHandler& mesgHandler, const std::atomic_bool& shouldCancel, ReadGrainMapper3DInputValues* inputValues); + ~ReadGrainMapper3D() noexcept = default; + + ReadGrainMapper3D(const ReadGrainMapper3D&) = delete; + ReadGrainMapper3D(ReadGrainMapper3D&&) noexcept = delete; + ReadGrainMapper3D& operator=(const ReadGrainMapper3D&) = delete; + ReadGrainMapper3D& operator=(ReadGrainMapper3D&&) noexcept = delete; + + Result<> operator()(); + + const std::atomic_bool& getCancel(); + +protected: + Result<> copyPhaseInformation(GrainMapperReader& reader, hid_t fileId) const; + Result<> copyDctData(GrainMapper3DUtilities::GrainMapperReader& reader, hid_t fileId) const; + Result<> copyAbsorptionData(GrainMapperReader& reader, hid_t fileId) const; + +private: + DataStructure& m_DataStructure; + const ReadGrainMapper3DInputValues* m_InputValues = nullptr; + const std::atomic_bool& m_ShouldCancel; + const IFilter::MessageHandler& m_MessageHandler; +}; + +} // namespace nx::core diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/ReadGrainMapper3DFilter.cpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/ReadGrainMapper3DFilter.cpp new file mode 100644 index 0000000000..3446f9636f --- /dev/null +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/ReadGrainMapper3DFilter.cpp @@ -0,0 +1,260 @@ +#include "ReadGrainMapper3DFilter.hpp" + +#include "OrientationAnalysis/Filters/Algorithms/ReadGrainMapper3D.hpp" +#include "OrientationAnalysis/utilities/GrainMapper3DUtilities.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/Parameters/BoolParameter.hpp" +#include "simplnx/Parameters/DataGroupCreationParameter.hpp" +#include "simplnx/Parameters/DataObjectNameParameter.hpp" +#include "simplnx/Parameters/FileSystemPathParameter.hpp" + +#include + +namespace fs = std::filesystem; + +using namespace nx::core; +using namespace GrainMapper3DUtilities; + +namespace +{ + +} // namespace + +namespace nx::core +{ +//------------------------------------------------------------------------------ +std::string ReadGrainMapper3DFilter::name() const +{ + return FilterTraits::name.str(); +} + +//------------------------------------------------------------------------------ +std::string ReadGrainMapper3DFilter::className() const +{ + return FilterTraits::className; +} + +//------------------------------------------------------------------------------ +Uuid ReadGrainMapper3DFilter::uuid() const +{ + return FilterTraits::uuid; +} + +//------------------------------------------------------------------------------ +std::string ReadGrainMapper3DFilter::humanName() const +{ + return "Read GrainMapper3D File"; +} + +//------------------------------------------------------------------------------ +std::vector ReadGrainMapper3DFilter::defaultTags() const +{ + return {className(), "Reader", "XNovo", "GrainMapper", "HDF5"}; +} + +//------------------------------------------------------------------------------ +Parameters ReadGrainMapper3DFilter::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", "The input .hdf5 file path", fs::path("input.h5"), FileSystemPathParameter::ExtensionsType{".h5"}, + FileSystemPathParameter::PathType::InputFile)); + + params.insert( + std::make_unique(k_ConvertPhaseToInt32_Key, "Create Compatible Phase Data", "Native Phases data value is uint8. Convert to Int32 for better filter compatibility", true)); + params.insert(std::make_unique(k_ConvertOrientationData_Key, "Create Compatible Orientation Data", + "Orientation data such as Quaternions and Rodrigues vectors will be converted to be DREAM3D-NX compatible", true)); + + params.insertSeparator(Parameters::Separator{"LabDCT Data"}); + params.insertLinkableParameter(std::make_unique(k_ReadLabDCT_Key, "Read LabDCT Data", "Read the LabDCT Data", true)); + params.insert(std::make_unique(k_CreatedDCTImageGeometryPath_Key, "Image Geometry", "The path to the created Image Geometry", DataPath({"LabDCT"}))); + 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.insert(std::make_unique(k_CellEnsembleAttributeMatrixName_Key, "Ensemble Attribute Matrix", "The Attribute Matrix where the phase information is stored.", + "Cell Ensemble Data")); + + params.insertSeparator(Parameters::Separator{"AbsorptionCT Data"}); + params.insertLinkableParameter(std::make_unique(k_ReadAbsorptionCT_Key, "Read AbsorptionCT Data", "Read the AbsorptionCT data", true)); + params.insert(std::make_unique(k_CreatedAbsorptionGeometryPath_Key, "Image Geometry", "The path to the created Image Geometry", DataPath({"AbsorptionCT"}))); + params.insert(std::make_unique(k_CellAbsorptionAttributeMatrixName_Key, "Cell Attribute Matrix", "The name of the cell data attribute matrix for the created Image Geometry", + ImageGeom::k_CellDataName)); + + // Associate the Linkable Parameter(s) to the children parameters that they control + params.linkParameters(k_ReadLabDCT_Key, k_CreatedDCTImageGeometryPath_Key, true); + params.linkParameters(k_ReadLabDCT_Key, k_CellAttributeMatrixName_Key, true); + params.linkParameters(k_ReadLabDCT_Key, k_CellEnsembleAttributeMatrixName_Key, true); + + params.linkParameters(k_ReadAbsorptionCT_Key, k_CreatedAbsorptionGeometryPath_Key, true); + params.linkParameters(k_ReadAbsorptionCT_Key, k_CellAbsorptionAttributeMatrixName_Key, true); + + return params; +} + +//------------------------------------------------------------------------------ +IFilter::UniquePointer ReadGrainMapper3DFilter::clone() const +{ + return std::make_unique(); +} + +//------------------------------------------------------------------------------ +IFilter::VersionType ReadGrainMapper3DFilter::parametersVersion() const +{ + return 1; +} + +//------------------------------------------------------------------------------ +IFilter::PreflightResult ReadGrainMapper3DFilter::preflightImpl(const DataStructure& dataStructure, const Arguments& filterArgs, const MessageHandler& messageHandler, + const std::atomic_bool& shouldCancel) const +{ + auto pInputFileValue = filterArgs.value(k_InputFile_Key); + auto pConvertPhaseData = filterArgs.value(k_ConvertPhaseToInt32_Key); + auto pConvertOrientationData = filterArgs.value(k_ConvertOrientationData_Key); + + auto pReadLabDCT = filterArgs.value(k_ReadLabDCT_Key); + auto pLabDCTImageGeometryPath = filterArgs.value(k_CreatedDCTImageGeometryPath_Key); + auto pLabDCTCellAttributeMatrixNameValue = filterArgs.value(k_CellAttributeMatrixName_Key); + auto pCellEnsembleAttributeMatrixNameValue = filterArgs.value(k_CellEnsembleAttributeMatrixName_Key); + + auto pReadAbsorptionCT = filterArgs.value(k_ReadAbsorptionCT_Key); + auto pAbsorptionCTImageGeometryPath = filterArgs.value(k_CreatedAbsorptionGeometryPath_Key); + auto pAbsorptionCTCellAttributeMatrixNameValue = filterArgs.value(k_CellAbsorptionAttributeMatrixName_Key); + + PreflightResult preflightResult; + nx::core::Result resultOutputActions; + std::vector preflightUpdatedValues; + + GrainMapperReader reader(pInputFileValue.string(), pReadLabDCT, pReadAbsorptionCT); + Result<> result = reader.readHeaderOnly(); + if(result.invalid()) + { + auto badResult = MakePreflightErrorResult(result.errors().front().code, result.errors().front().message); + return badResult; + } + + if(!pReadLabDCT && !pReadAbsorptionCT) + { + resultOutputActions.warnings().push_back({-65432, "WARNING: No data is being read by this filter because both Read DCT and Read Absorption are both FALSE."}); + } + + // ************************************************************************** + // LAB DCT DATA SECTION + if(pReadLabDCT) + { + // create the DCT Image Geometry and it's attribute matrices + const std::vector dims = reader.getLabDCTDimensions(); + { + CreateImageGeometryAction::SpacingType spacing = reader.getLabDCTSpacing(); + std::vector origin = reader.getLabDCTOrigin(); + + auto createDataGroupAction = std::make_unique(pLabDCTImageGeometryPath, dims, origin, spacing, pLabDCTCellAttributeMatrixNameValue, IGeometry::LengthUnit::Millimeter); + resultOutputActions.value().appendAction(std::move(createDataGroupAction)); + } + + // Reverse the DCT Image Dimensions + const std::vector tupleDims = {dims[2], dims[1], dims[0]}; + + // Get the available Data sets + DataPath cellAMPath = pLabDCTImageGeometryPath.createChildPath(pLabDCTCellAttributeMatrixNameValue); + + auto nameToDataTypeMap = reader.getNameToDataTypeMap(); + auto nameToCompDimMap = reader.getNameToCompDimMap(); + auto availableDataSets = reader.getDctDatasetNames(); + for(const auto& dataSetName : availableDataSets) + { + if(pConvertPhaseData && dataSetName == GrainMapper3DUtilities::Constants::k_PhaseIdName) + { + resultOutputActions.value().appendAction( + std::make_unique(DataType::int32, tupleDims, std::vector{nameToCompDimMap[dataSetName]}, cellAMPath.createChildPath(dataSetName))); + } + else if(pConvertOrientationData && dataSetName == GrainMapper3DUtilities::Constants::k_RodriguesName) + { + resultOutputActions.value().appendAction(std::make_unique(nameToDataTypeMap[dataSetName], tupleDims, std::vector{4}, cellAMPath.createChildPath(dataSetName))); + } + else + { + resultOutputActions.value().appendAction( + std::make_unique(nameToDataTypeMap[dataSetName], tupleDims, std::vector{nameToCompDimMap[dataSetName]}, cellAMPath.createChildPath(dataSetName))); + } + } + + // read the DCT phase information + DataPath cellEnsembleAMPath = pLabDCTImageGeometryPath.createChildPath(pCellEnsembleAttributeMatrixNameValue); + + auto phases = reader.getPhaseInformation(); + std::vector ensembleTupleDims{phases.size() + 1}; + { + auto createAttributeMatrixAction = std::make_unique(cellEnsembleAMPath, ensembleTupleDims); + resultOutputActions.value().appendAction(std::move(createAttributeMatrixAction)); + } + + // create the cell ensemble arrays + { + auto createArrayAction = std::make_unique(DataType::uint32, ensembleTupleDims, std::vector{1}, cellEnsembleAMPath.createChildPath(GM3DConstants::k_CrystalStructures)); + resultOutputActions.value().appendAction(std::move(createArrayAction)); + } + { + auto createArrayAction = std::make_unique(DataType::float32, ensembleTupleDims, std::vector{6}, cellEnsembleAMPath.createChildPath(GM3DConstants::k_LatticeConstants)); + resultOutputActions.value().appendAction(std::move(createArrayAction)); + } + { + auto createArrayAction = std::make_unique(ensembleTupleDims, cellEnsembleAMPath.createChildPath(GM3DConstants::k_MaterialName)); + resultOutputActions.value().appendAction(std::move(createArrayAction)); + } + } + + // ************************************************************************** + // ABSORPTION DCT DATA SECTION + if(pReadAbsorptionCT) + { + // create the ABSORPTION Image Geometry and it's attribute matrices + const std::vector dims = reader.getAbsorptionCTDimensions(); + { + CreateImageGeometryAction::SpacingType spacing = reader.getAbsorptionCTSpacing(); + std::vector origin = reader.getAbsorptionCTOrigin(); + + auto createDataGroupAction = + std::make_unique(pAbsorptionCTImageGeometryPath, dims, origin, spacing, pAbsorptionCTCellAttributeMatrixNameValue, IGeometry::LengthUnit::Millimeter); + resultOutputActions.value().appendAction(std::move(createDataGroupAction)); + } + + // Reverse the ABSORPTION Image Dimensions + const std::vector tupleDims = {dims[2], dims[1], dims[0]}; + // Create the 'Data' data array + resultOutputActions.value().appendAction(std::make_unique( + DataType::uint16, tupleDims, std::vector{1ULL}, + pAbsorptionCTImageGeometryPath.createChildPath(pAbsorptionCTCellAttributeMatrixNameValue).createChildPath(GrainMapper3DUtilities::Constants::k_DataGroupName))); + } + + return {std::move(resultOutputActions), std::move(preflightUpdatedValues)}; +} + +//------------------------------------------------------------------------------ +Result<> ReadGrainMapper3DFilter::executeImpl(DataStructure& dataStructure, const Arguments& filterArgs, const PipelineFilter* pipelineNode, const MessageHandler& messageHandler, + const std::atomic_bool& shouldCancel) const +{ + ReadGrainMapper3DInputValues inputValues; + + inputValues.InputFile = filterArgs.value(k_InputFile_Key); + + inputValues.ReadDctData = filterArgs.value(k_ReadLabDCT_Key); + inputValues.DctImageGeometryPath = filterArgs.value(k_CreatedDCTImageGeometryPath_Key); + inputValues.DctCellAttributeMatrixName = filterArgs.value(k_CellAttributeMatrixName_Key); + inputValues.DctCellEnsembleAttributeMatrixName = filterArgs.value(k_CellEnsembleAttributeMatrixName_Key); + inputValues.ConvertPhaseData = filterArgs.value(k_ConvertPhaseToInt32_Key); + inputValues.ConvertOrientationData = filterArgs.value(k_ConvertOrientationData_Key); + + inputValues.ReadAbsorptionData = filterArgs.value(k_ReadAbsorptionCT_Key); + inputValues.AbsorptionImageGeometryPath = filterArgs.value(k_CreatedAbsorptionGeometryPath_Key); + inputValues.AbsorptionCellAttributeMatrixName = filterArgs.value(k_CellAbsorptionAttributeMatrixName_Key); + + return ReadGrainMapper3D(dataStructure, messageHandler, shouldCancel, &inputValues)(); +} +} // namespace nx::core diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/ReadGrainMapper3DFilter.hpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/ReadGrainMapper3DFilter.hpp new file mode 100644 index 0000000000..a20937f137 --- /dev/null +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/ReadGrainMapper3DFilter.hpp @@ -0,0 +1,114 @@ +#pragma once + +#include "OrientationAnalysis/OrientationAnalysis_export.hpp" + +#include "simplnx/Filter/FilterTraits.hpp" +#include "simplnx/Filter/IFilter.hpp" + +namespace nx::core +{ +/** + * @class ReadGrainMapper3DFilter + * @brief This filter determines the average C-axis location of each Feature + */ +class ORIENTATIONANALYSIS_EXPORT ReadGrainMapper3DFilter : public IFilter +{ +public: + ReadGrainMapper3DFilter() = default; + ~ReadGrainMapper3DFilter() noexcept override = default; + + ReadGrainMapper3DFilter(const ReadGrainMapper3DFilter&) = delete; + ReadGrainMapper3DFilter(ReadGrainMapper3DFilter&&) noexcept = delete; + + ReadGrainMapper3DFilter& operator=(const ReadGrainMapper3DFilter&) = delete; + ReadGrainMapper3DFilter& operator=(ReadGrainMapper3DFilter&&) noexcept = delete; + + // Parameter Keys + static inline constexpr StringLiteral k_InputFile_Key = "input_file"; + static inline constexpr StringLiteral k_ReadLabDCT_Key = "read_lab_dct_data"; + static inline constexpr StringLiteral k_CreatedDCTImageGeometryPath_Key = "output_dct_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"; + static inline constexpr StringLiteral k_ConvertPhaseToInt32_Key = "convert_phase_to_int32"; + static inline constexpr StringLiteral k_ConvertOrientationData_Key = "convert_orientation_data"; + static inline constexpr StringLiteral k_ReadAbsorptionCT_Key = "read_absorption_ct_data"; + static inline constexpr StringLiteral k_CreatedAbsorptionGeometryPath_Key = "output_absorption_image_geometry_path"; + static inline constexpr StringLiteral k_CellAbsorptionAttributeMatrixName_Key = "cell_absorption_attribute_matrix_name"; + + /** + * @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 ds 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 ds 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, ReadGrainMapper3DFilter, "ed46afcf-de32-4f37-98bc-8f0fd4b3c122"); +/* LEGACY UUID FOR THIS FILTER */ diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/utilities/GrainMapper3DUtilities.cpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/utilities/GrainMapper3DUtilities.cpp new file mode 100644 index 0000000000..ab725b710e --- /dev/null +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/utilities/GrainMapper3DUtilities.cpp @@ -0,0 +1,353 @@ +// +// Created by Michael Jackson on 8/19/24. +// + +#include "GrainMapper3DUtilities.hpp" + +#include "H5Support/H5Lite.h" +#include "H5Support/H5ScopedSentinel.h" +#include "H5Support/H5Utilities.h" + +#include + +using namespace nx::core; +using namespace H5Support; + +namespace GM3DConst = GrainMapper3DUtilities::Constants; + +namespace GrainMapper3DUtilities +{ + +const std::map k_NameToDataTypeMap = { + {GM3DConst::k_CompletenessName, DataType::float32}, {GM3DConst::k_GrainIdName, DataType::int32}, {GM3DConst::k_MaskName, DataType::uint8}, + {GM3DConst::k_PhaseIdName, DataType::uint8}, {GM3DConst::k_RodriguesName, DataType::float32}, {GM3DConst::k_EulerZXZName, DataType::float32}, + {GM3DConst::k_EulerZYZName, DataType::float32}, {GM3DConst::k_QuaternionName, DataType::float32}, {GM3DConst::k_IPF001Name, DataType::uint8}, + {GM3DConst::k_IPF010Name, DataType::uint8}, {GM3DConst::k_IPF100Name, DataType::uint8}}; + +const std::map k_NameToCompDimMap = {{GM3DConst::k_CompletenessName, 1}, {GM3DConst::k_GrainIdName, 1}, {GM3DConst::k_MaskName, 1}, {GM3DConst::k_PhaseIdName, 1}, + {GM3DConst::k_RodriguesName, 3}, {GM3DConst::k_EulerZXZName, 3}, {GM3DConst::k_EulerZYZName, 3}, {GM3DConst::k_QuaternionName, 4}, + {GM3DConst::k_IPF001Name, 3}, {GM3DConst::k_IPF010Name, 3}, {GM3DConst::k_IPF100Name, 3}}; + +int32_t GetLaueIndexFromSpaceGroup(int32_t spaceGroupId) +{ + // clang-format off + std::array sgpg = {1, 2, 3, 6, 10, 16, 25, 47, 75, 81, 83, 89, 99, 111, 123, 143, 147, 149, 156, 162, 168, 174, 175, 177, 183, 187, 191, 195, 200, 207, 215, 221}; + std::array pgLaue = {1, 1, 2, 2, 2, 22, 22, 22, 4, 4, 4, 42, 42, 42, 42, 3, 3, 32, 32, 32, 6, 6, 6, 62, 62, 62, 62, 23, 23, 43, 43, 43}; + // clang-format on + size_t pgIndex = sgpg.size() - 1; + for(size_t i = 0; i < sgpg.size(); i++) + { + if(sgpg[i] > spaceGroupId) + { + pgIndex = i - 1; + break; + } + } + + size_t value = pgLaue.at(pgIndex); + switch(value) + { + case 1: // TriclinicOps + return 4; + case 2: // MonoclinicOps + return 5; + case 22: // OrthoRhombicOps + return 6; + case 4: // TetragonalLowOps + return 7; + case 42: // TetragonalOps + return 8; + case 3: // TrigonalLowOps + return 9; + case 32: // TrigonalOps + return 10; + case 6: // HexagonalLowOps + return 2; + case 62: // HexagonalOps + return 0; + case 23: // CubicLowOps + return 3; + case 43: // CubicOps + return 1; + default: + return 999; + } +} + +GrainMapperReader::GrainMapperReader(const std::string& filePath, bool readDctData, bool readAbsorptionData) +: m_FileName(filePath) +, m_ReadDctData(readDctData) +, m_ReadAbsorptionData(readAbsorptionData) +{ +} + +GrainMapperReader::~GrainMapperReader() = default; + +std::vector GrainMapperReader::getLabDCTDimensions() const +{ + return m_LabDctDimensions; +} + +std::vector GrainMapperReader::getLabDCTSpacing() const +{ + return {static_cast(m_LabDctSpacing[0]), static_cast(m_LabDctSpacing[1]), static_cast(m_LabDctSpacing[2])}; +} + +std::vector GrainMapperReader::getLabDCTOrigin() const +{ + return {static_cast(m_LabDctOrigin[0]), static_cast(m_LabDctOrigin[1]), static_cast(m_LabDctOrigin[2])}; +} + +std::vector GrainMapperReader::getAbsorptionCTDimensions() const +{ + return m_AbsorptionCTDimensions; +} + +std::vector GrainMapperReader::getAbsorptionCTSpacing() const +{ + return {static_cast(m_AbsorptionCTSpacing[0]), static_cast(m_AbsorptionCTSpacing[1]), static_cast(m_AbsorptionCTSpacing[2])}; +} + +std::vector GrainMapperReader::getAbsorptionCTOrigin() const +{ + return {static_cast(m_AbsorptionCTOrigin[0]), static_cast(m_AbsorptionCTOrigin[1]), static_cast(m_AbsorptionCTOrigin[2])}; +} + +std::map GrainMapperReader::getNameToDataTypeMap() const +{ + return GrainMapper3DUtilities::k_NameToDataTypeMap; +} + +std::map GrainMapperReader::getNameToCompDimMap() const +{ + return GrainMapper3DUtilities::k_NameToCompDimMap; +} + +std::vector GrainMapperReader::getDctDatasetNames() const +{ + return m_AvailableDCTDatasets; +} + +std::vector GrainMapperReader::getPhaseInformation() const +{ + return m_PhaseInfos; +} + +nx::core::Result<> GrainMapperReader::readLabDCTHeader(hid_t fileId) +{ + if(!m_ReadDctData) + { + return {}; + } + // Get the LabDCT Image Geometry Dimensions + hid_t labDctGid = H5Gopen(fileId, Constants::k_LabDCTGroupName.c_str(), H5P_DEFAULT); + if(labDctGid < 0) + { + return MakeErrorResult(-38602, "GrainMapperReader: Error opening group /LabDCT"); + } + H5ScopedGroupSentinel sentinel(labDctGid, true); + + std::vector extents; + herr_t error = H5Lite::readVectorDataset(labDctGid, Constants::k_ExtentName, extents); + if(error < 0) + { + return MakeErrorResult(-38603, "GrainMapperReader: Error reading data set /LabDCT/Extent"); + } + + error = H5Lite::readVectorDataset(labDctGid, Constants::k_SpacingName, m_LabDctSpacing); + if(error < 0) + { + return MakeErrorResult(-38604, "GrainMapperReader: Error reading data set /LabDCT/Spacing"); + } + + m_LabDctDimensions = + std::vector{static_cast(extents[0] / m_LabDctSpacing[0]), static_cast(extents[1] / m_LabDctSpacing[1]), static_cast(extents[2] / m_LabDctSpacing[2])}; + + std::vector center; + error = H5Lite::readVectorDataset(labDctGid, Constants::k_CenterName, center); + if(error < 0) + { + return MakeErrorResult(-38605, "GrainMapperReader: Error reading data set /LabDCT/Center"); + } + + std::vector virtualShift; + error = H5Lite::readVectorDataset(labDctGid, Constants::k_VirtualShift, virtualShift); + if(error < 0) + { + return MakeErrorResult(-38608, "GrainMapperReader: Error reading data set /LabDCT/VirtualShift"); + } + + m_LabDctOrigin[0] = (center[0] - (extents[0] * 0.5)) + virtualShift[0]; + m_LabDctOrigin[1] = (center[1] - (extents[1] * 0.5)) + virtualShift[1]; + m_LabDctOrigin[2] = (center[2] - (extents[2] * 0.5)) + virtualShift[2]; + + error = findAvailableDctDatasets(labDctGid); + if(error < 0) + { + return MakeErrorResult(-38606, "GrainMapperReader: Error parsing available data sets"); + } + error = readPhaseInfo(fileId); + if(error < 0) + { + return MakeErrorResult(-38607, fmt::format("GrainMapperReader: Error reading /PhaseInfo")); + } + return {}; +} + +nx::core::Result<> GrainMapperReader::readAbsorptionHeader(hid_t fileId) +{ + if(!m_ReadAbsorptionData) + { + return {}; + } + + hid_t gid = H5Gopen(fileId, Constants::k_AbsorptionCTName.c_str(), H5P_DEFAULT); + if(gid < 0) + { + return MakeErrorResult(-38602, "GrainMapperReader: Error opening group /AbsorptionCT"); + } + H5ScopedGroupSentinel sentinel(gid, true); + + std::vector extents; + herr_t error = H5Lite::readVectorDataset(gid, Constants::k_ExtentName, extents); + if(error < 0) + { + return MakeErrorResult(-38603, "GrainMapperReader: Error reading data set /LabDCT/Extent"); + } + + error = H5Lite::readVectorDataset(gid, Constants::k_SpacingName, m_AbsorptionCTSpacing); + if(error < 0) + { + return MakeErrorResult(-38604, "GrainMapperReader: Error reading data set /LabDCT/Spacing"); + } + + m_AbsorptionCTDimensions = std::vector{static_cast(extents[0] / m_AbsorptionCTSpacing[0]), static_cast(extents[1] / m_AbsorptionCTSpacing[1]), + static_cast(extents[2] / m_AbsorptionCTSpacing[2])}; + + std::vector center; + error = H5Lite::readVectorDataset(gid, Constants::k_CenterName, center); + if(error < 0) + { + return MakeErrorResult(-38605, "GrainMapperReader: Error reading data set /AbsorptionCT/Center"); + } + + std::vector virtualShift; + error = H5Lite::readVectorDataset(gid, Constants::k_VirtualShift, virtualShift); + if(error < 0) + { + return MakeErrorResult(-38608, "GrainMapperReader: Error reading data set /AbsorptionCT/VirtualShift"); + } + + m_AbsorptionCTOrigin[0] = (center[0] - (extents[0] * 0.5)) + virtualShift[0]; + m_AbsorptionCTOrigin[1] = (center[1] - (extents[1] * 0.5)) + virtualShift[1]; + m_AbsorptionCTOrigin[2] = (center[2] - (extents[2] * 0.5)) + virtualShift[2]; + + return {}; +} + +Result<> GrainMapperReader::readHeaderOnly() +{ + Result<> result; + + hid_t fileId = H5Support::H5Utilities::openFile(m_FileName, true); + if(fileId < 0) + { + return MakeErrorResult(-39600, fmt::format("Grain Mapper 3D File '{}' could not be opened.", m_FileName)); + } + auto sentinel = H5Support::H5ScopedFileSentinel(fileId, false); + + result = readLabDCTHeader(fileId); + if(result.invalid()) + { + return result; + } + + result = readAbsorptionHeader(fileId); + if(result.invalid()) + { + return result; + } + + return {}; +} + +herr_t GrainMapperReader::findAvailableDctDatasets(hid_t labDctGid) +{ + // Now check that each of the known data sets exist + // Get the Image Geometry Dimensions + hid_t dataGid = H5Gopen(labDctGid, Constants::k_DataGroupName.c_str(), H5P_DEFAULT); + if(dataGid < 0) + { + return dataGid; + } + auto groupSentinel = H5Support::H5ScopedGroupSentinel(dataGid, true); + + for(const auto& entry : GrainMapper3DUtilities::k_NameToDataTypeMap) + { + if(H5Lite::datasetExists(dataGid, entry.first)) + { + m_AvailableDCTDatasets.push_back(entry.first); + } + } + return 0; +} + +herr_t GrainMapperReader::readPhaseInfo(hid_t parentId) +{ + // Get the Phase Information + hid_t phaseInfoGid = H5Gopen(parentId, Constants::k_PhaseInfoName.c_str(), H5P_DEFAULT); + if(phaseInfoGid < 0) + { + return phaseInfoGid; + } + auto groupSentinel = H5Support::H5ScopedGroupSentinel(phaseInfoGid, true); + std::list phaseNames; + herr_t error = H5Utilities::getGroupObjects(phaseInfoGid, H5Utilities::CustomHDFDataTypes::Group, phaseNames); + if(error < 0) + { + return error; + } + m_PhaseInfos.clear(); + + // Now we know how many phases we have, we need to programmatically generate those phase names + // in order to keep them consistent. Yep, someone didn't really think through the parsing of this + // or assumptions are being made about the order that HDF5 is going to give them back to you. Either + // is bad. + for(int i = 0; i < phaseNames.size(); i++) + { + std::string phaseName = fmt::format("Phase{:02}", i + 1); + + hid_t phaseGid = H5Gopen(phaseInfoGid, phaseName.c_str(), H5P_DEFAULT); + auto phaseDGidSentinel = H5Support::H5ScopedGroupSentinel(phaseGid, true); + + GrainMapperPhase phase; + error = H5Lite::readStringDataset(phaseGid, Constants::k_Name, phase.Name); + if(error < 0) + { + return error; + } + + error = H5Lite::readStringDataset(phaseGid, Constants::k_Name, phase.UniversalHermannMauguin); + if(error < 0) + { + return error; + } + + error = H5Lite::readScalarDataset(phaseGid, Constants::k_SpaceGroupName, phase.SpaceGroup); + if(error < 0) + { + return error; + } + + error = H5Lite::readVectorDataset(phaseGid, Constants::k_UnitCellName, phase.UnitCell); + if(error < 0) + { + return error; + } + + m_PhaseInfos.push_back(phase); + } + return 0; +} + +} // namespace GrainMapper3DUtilities diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/utilities/GrainMapper3DUtilities.hpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/utilities/GrainMapper3DUtilities.hpp new file mode 100644 index 0000000000..e29ab81d7f --- /dev/null +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/utilities/GrainMapper3DUtilities.hpp @@ -0,0 +1,115 @@ +#pragma once + +#include "OrientationAnalysis/OrientationAnalysis_export.hpp" + +#include "simplnx/Common/Array.hpp" +#include "simplnx/Common/Result.hpp" +#include "simplnx/Common/Types.hpp" +#include "simplnx/DataStructure/Geometry/ImageGeom.hpp" + +#include + +#include +#include +#include + +namespace GrainMapper3DUtilities +{ + +namespace Constants +{ +const std::string k_LabDCTGroupName("LabDCT"); +const std::string k_AbsorptionCTName("AbsorptionCT"); +const std::string k_ProjectInfoName("ProjectInfo"); +const std::string k_VersionName("Version"); + +const std::string k_ExtentName("Extent"); +const std::string k_SpacingName("Spacing"); +const std::string k_CenterName("Center"); +const std::string k_VirtualShift("VirtualShift"); + +const std::string k_DataGroupName("Data"); +const std::string k_CompletenessName("Completeness"); +const std::string k_GrainIdName("GrainId"); +const std::string k_MaskName("Mask"); +const std::string k_PhaseIdName("PhaseId"); +const std::string k_RodriguesName("Rodrigues"); + +const std::string k_EulerZXZName("EulerZXZ"); +const std::string k_EulerZYZName("EulerZYZ"); +const std::string k_QuaternionName("Quaternion"); +const std::string k_IPF001Name("IPF001"); +const std::string k_IPF010Name("IPF010"); +const std::string k_IPF100Name("IPF100"); + +// **************************************************************************** +// Phase Constants +const std::string k_PhaseInfoName("PhaseInfo"); +const std::string k_Name("Name"); +const std::string k_SpaceGroupName("SpaceGroup"); +const std::string k_UnitCellName("UnitCell"); +const std::string k_UniversalHermannMauguinName("UniversalHermannMauguin"); + +} // namespace Constants + +int32_t GetLaueIndexFromSpaceGroup(int32_t spaceGroupId); + +/** + * @brief + */ +class ORIENTATIONANALYSIS_EXPORT GrainMapperReader +{ +public: + explicit GrainMapperReader(const std::string& filePath, bool readDctData, bool readAbsorptionData); + ~GrainMapperReader(); + + typedef struct + { + std::string Name; + int32_t SpaceGroup; + std::vector UnitCell; // ABC, Alpha, Beta, Gamma + std::string UniversalHermannMauguin; + } GrainMapperPhase; + + nx::core::Result<> readHeaderOnly(); + + std::vector getLabDCTDimensions() const; + std::vector getLabDCTSpacing() const; + std::vector getLabDCTOrigin() const; + + std::vector getAbsorptionCTDimensions() const; + std::vector getAbsorptionCTSpacing() const; + std::vector getAbsorptionCTOrigin() const; + + nx::core::Result<> readLabDCTHeader(hid_t fileId); + nx::core::Result<> readAbsorptionHeader(hid_t fileId); + + std::vector getDctDatasetNames() const; + std::map getNameToDataTypeMap() const; + std::map getNameToCompDimMap() const; + std::vector getPhaseInformation() const; + herr_t readPhaseInfo(hid_t parentId); + herr_t findAvailableDctDatasets(hid_t parentId); + +private: + bool m_ReadDctData = false; + bool m_ReadAbsorptionData = false; + + std::string m_ErrorMessage = {}; + std::string m_FileName = {}; + std::string m_HDF5Path = {}; + std::string m_OINAVersion = {}; + + std::vector m_LabDctDimensions; + std::vector m_LabDctSpacing = {1.0, 1.0, 1.0}; + std::vector m_LabDctOrigin = {0.0, 0.0, 0.0}; + + std::vector m_AbsorptionCTDimensions; + std::vector m_AbsorptionCTSpacing = {1.0, 1.0, 1.0}; + std::vector m_AbsorptionCTOrigin = {0.0, 0.0, 0.0}; + + std::vector m_AvailableDCTDatasets; + std::vector m_PhaseInfos; +}; + +}; // namespace GrainMapper3DUtilities diff --git a/src/Plugins/OrientationAnalysis/test/CMakeLists.txt b/src/Plugins/OrientationAnalysis/test/CMakeLists.txt index b29fd72e2f..25e2f3b9bf 100644 --- a/src/Plugins/OrientationAnalysis/test/CMakeLists.txt +++ b/src/Plugins/OrientationAnalysis/test/CMakeLists.txt @@ -43,6 +43,7 @@ set(${PLUGIN_NAME}UnitTest_SRCS ReadAngDataTest.cpp ReadCtfDataTest.cpp ReadEnsembleInfoTest.cpp + ReadGrainMapper3DTest.cpp ReadH5EbsdTest.cpp ReadH5EspritDataTest.cpp ReadH5OimDataTest.cpp @@ -140,6 +141,7 @@ if(EXISTS "${DREAM3D_DATA_DIR}" AND SIMPLNX_DOWNLOAD_TEST_FILES) download_test_data(DREAM3D_DATA_DIR ${DREAM3D_DATA_DIR} ARCHIVE_NAME Small_IN100_h5ebsd.tar.gz SHA512 31e606285ea9e8235dcb5f608fd2b252a5ab1492abd975e5ec33a21d083aa9720fe16fb8f752742c140f40e963d692f1a46256b9d36e96b1b09796c1e4ea3db9) download_test_data(DREAM3D_DATA_DIR ${DREAM3D_DATA_DIR} ARCHIVE_NAME so3_cubic_high_ipf_001.tar.gz SHA512 dfe4598cd4406e8b83f244302dc4fe0d4367527835c5ddd6567fe8d8ab3484d5b10ba24a8bb31db269256ec0b5272daa4340eedb5a8b397755541b32dd616b85) download_test_data(DREAM3D_DATA_DIR ${DREAM3D_DATA_DIR} ARCHIVE_NAME write_stats_gen_odf_angle_file.tar.gz SHA512 be3f663aae1f78e5b789200421534ed9fe293187ec3514796ac8177128b34ded18bb9a98b8e838bb283f9818ac30dc4b19ec379bdd581b1a98eb36d967cdd319) + download_test_data(DREAM3D_DATA_DIR ${DREAM3D_DATA_DIR} ARCHIVE_NAME GrainMapper3D_Test_Files.tar.gz SHA512 cbd9cde2528d62de6cffecd125795b571f8b1a41c8eebbbbac5908f5382ac3c3c3ea56efc34b6e6ca713055f4fd13485aa6dd5acb9a69e9d4b3b16941ed1f96f) endif() diff --git a/src/Plugins/OrientationAnalysis/test/ReadGrainMapper3DTest.cpp b/src/Plugins/OrientationAnalysis/test/ReadGrainMapper3DTest.cpp new file mode 100644 index 0000000000..d97850d8c0 --- /dev/null +++ b/src/Plugins/OrientationAnalysis/test/ReadGrainMapper3DTest.cpp @@ -0,0 +1,139 @@ +#include + +#include "simplnx/Parameters/ArrayCreationParameter.hpp" +#include "simplnx/Parameters/BoolParameter.hpp" +#include "simplnx/Parameters/FileSystemPathParameter.hpp" +#include "simplnx/UnitTest/UnitTestCommon.hpp" + +#include "OrientationAnalysis/Filters/ReadGrainMapper3DFilter.hpp" +#include "OrientationAnalysis/OrientationAnalysis_test_dirs.hpp" + +#include +namespace fs = std::filesystem; + +using namespace nx::core; +using namespace nx::core::Constants; + +namespace +{ +const std::string k_LabDCTGeometryName("LabDCT"); +const std::string k_AbsorptionCTGeometryName("AbsorptionCT"); + +} // namespace + +TEST_CASE("SimplnxReview::ReadGrainMapper3D:Default_Parameters", "[SimplnxReview][ReadGrainMapper3D]") +{ + + const nx::core::UnitTest::TestFileSentinel testDataSentinel(nx::core::unit_test::k_CMakeExecutable, nx::core::unit_test::k_TestFilesDir, "GrainMapper3D_Test_Files.tar.gz", + "GrainMapper3D_Test_Files"); + + // Read Exemplar DREAM3D File Filter + auto exemplarFilePath = fs::path(fmt::format("{}/GrainMapper3D_Test_Files/7_0_SimulatedMultiPhase.dream3d", unit_test::k_TestFilesDir)); + DataStructure dataStructure = UnitTest::LoadDataStructure(exemplarFilePath); + + DataPath computedDCTGeometryPath({fmt::format("{} computed", k_LabDCTGeometryName)}); + DataPath computedAbsorptionCTGeometryPath({fmt::format("{} computed", k_AbsorptionCTGeometryName)}); + + DataPath exemplarDCTGeometryPath({fmt::format("{} (default)", k_LabDCTGeometryName)}); + DataPath exemplarAbsorptionCTGeometryPath({fmt::format("{} (default)", k_AbsorptionCTGeometryName)}); + + auto inputGM3DFilePath = fs::path(fmt::format("{}/GrainMapper3D_Test_Files/SimulatedMultiPhase.h5", unit_test::k_TestFilesDir)); + { + // Instantiate the filter, a DataStructure object and an Arguments Object + ReadGrainMapper3DFilter filter; + Arguments args; + + // Create default Parameters for the filter. + // Create default Parameters for the filter. + args.insertOrAssign(ReadGrainMapper3DFilter::k_InputFile_Key, std::make_any(fs::path(inputGM3DFilePath))); + args.insertOrAssign(ReadGrainMapper3DFilter::k_ReadLabDCT_Key, std::make_any(true)); + args.insertOrAssign(ReadGrainMapper3DFilter::k_CreatedDCTImageGeometryPath_Key, std::make_any(computedDCTGeometryPath)); + args.insertOrAssign(ReadGrainMapper3DFilter::k_CellAttributeMatrixName_Key, std::make_any(k_Cell_Data)); + args.insertOrAssign(ReadGrainMapper3DFilter::k_CellEnsembleAttributeMatrixName_Key, std::make_any(k_EnsembleAttributeMatrix)); + + args.insertOrAssign(ReadGrainMapper3DFilter::k_ConvertPhaseToInt32_Key, std::make_any(true)); + args.insertOrAssign(ReadGrainMapper3DFilter::k_ConvertOrientationData_Key, std::make_any(true)); + + args.insertOrAssign(ReadGrainMapper3DFilter::k_ReadAbsorptionCT_Key, std::make_any(true)); + args.insertOrAssign(ReadGrainMapper3DFilter::k_CreatedAbsorptionGeometryPath_Key, std::make_any(computedAbsorptionCTGeometryPath)); + args.insertOrAssign(ReadGrainMapper3DFilter::k_CellAbsorptionAttributeMatrixName_Key, std::make_any(k_Cell_Data)); + + // Preflight the filter and check result + auto preflightResult = filter.preflight(dataStructure, args); + 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); + } + + // Write the DataStructure out to the file system +#ifdef SIMPLNX_WRITE_TEST_OUTPUT + WriteTestDataStructure(dataStructure, fs::path(fmt::format("{}/read_grainmapper_3d_default.dream3d", unit_test::k_BinaryTestOutputDir))); +#endif + + UnitTest::CompareImageGeometry(dataStructure, exemplarDCTGeometryPath, computedDCTGeometryPath); + UnitTest::CompareImageGeometry(dataStructure, exemplarAbsorptionCTGeometryPath, computedAbsorptionCTGeometryPath); + + UnitTest::CompareExemplarToGenerateAttributeMatrix(dataStructure, exemplarDCTGeometryPath.createChildPath(k_Cell_Data), dataStructure, computedDCTGeometryPath.createChildPath(k_Cell_Data)); + UnitTest::CompareExemplarToGenerateAttributeMatrix(dataStructure, exemplarAbsorptionCTGeometryPath.createChildPath(k_Cell_Data), dataStructure, + computedAbsorptionCTGeometryPath.createChildPath(k_Cell_Data)); +} + +TEST_CASE("SimplnxReview::ReadGrainMapper3D:NonCompatible_Parameters", "[SimplnxReview][ReadGrainMapper3D]") +{ + + const nx::core::UnitTest::TestFileSentinel testDataSentinel(nx::core::unit_test::k_CMakeExecutable, nx::core::unit_test::k_TestFilesDir, "GrainMapper3D_Test_Files.tar.gz", + "GrainMapper3D_Test_Files"); + + // Read Exemplar DREAM3D File Filter + auto exemplarFilePath = fs::path(fmt::format("{}/GrainMapper3D_Test_Files/7_0_SimulatedMultiPhase.dream3d", unit_test::k_TestFilesDir)); + DataStructure dataStructure = UnitTest::LoadDataStructure(exemplarFilePath); + + DataPath computedDCTGeometryPath({fmt::format("{} computed", k_LabDCTGeometryName)}); + DataPath computedAbsorptionCTGeometryPath({fmt::format("{} computed", k_AbsorptionCTGeometryName)}); + + DataPath exemplarDCTGeometryPath({fmt::format("{} (non-compatible)", k_LabDCTGeometryName)}); + DataPath exemplarAbsorptionCTGeometryPath({fmt::format("{} (non-compatible)", k_AbsorptionCTGeometryName)}); + + auto inputGM3DFilePath = fs::path(fmt::format("{}/GrainMapper3D_Test_Files/SimulatedMultiPhase.h5", unit_test::k_TestFilesDir)); + { + // Instantiate the filter, a DataStructure object and an Arguments Object + ReadGrainMapper3DFilter filter; + Arguments args; + + // Create default Parameters for the filter. + // Create default Parameters for the filter. + args.insertOrAssign(ReadGrainMapper3DFilter::k_InputFile_Key, std::make_any(fs::path(inputGM3DFilePath))); + args.insertOrAssign(ReadGrainMapper3DFilter::k_ReadLabDCT_Key, std::make_any(true)); + args.insertOrAssign(ReadGrainMapper3DFilter::k_CreatedDCTImageGeometryPath_Key, std::make_any(computedDCTGeometryPath)); + args.insertOrAssign(ReadGrainMapper3DFilter::k_CellAttributeMatrixName_Key, std::make_any(k_Cell_Data)); + args.insertOrAssign(ReadGrainMapper3DFilter::k_CellEnsembleAttributeMatrixName_Key, std::make_any(k_EnsembleAttributeMatrix)); + + args.insertOrAssign(ReadGrainMapper3DFilter::k_ConvertPhaseToInt32_Key, std::make_any(false)); + args.insertOrAssign(ReadGrainMapper3DFilter::k_ConvertOrientationData_Key, std::make_any(false)); + + args.insertOrAssign(ReadGrainMapper3DFilter::k_ReadAbsorptionCT_Key, std::make_any(true)); + args.insertOrAssign(ReadGrainMapper3DFilter::k_CreatedAbsorptionGeometryPath_Key, std::make_any(computedAbsorptionCTGeometryPath)); + args.insertOrAssign(ReadGrainMapper3DFilter::k_CellAbsorptionAttributeMatrixName_Key, std::make_any(k_Cell_Data)); + + // Preflight the filter and check result + auto preflightResult = filter.preflight(dataStructure, args); + 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); + } +// Write the DataStructure out to the file system +#ifdef SIMPLNX_WRITE_TEST_OUTPUT + WriteTestDataStructure(dataStructure, fs::path(fmt::format("{}/read_grainmapper_3d_non_compatible.dream3d", unit_test::k_BinaryTestOutputDir))); +#endif + + UnitTest::CompareImageGeometry(dataStructure, exemplarDCTGeometryPath, computedDCTGeometryPath); + UnitTest::CompareImageGeometry(dataStructure, exemplarAbsorptionCTGeometryPath, computedAbsorptionCTGeometryPath); + + UnitTest::CompareExemplarToGenerateAttributeMatrix(dataStructure, exemplarDCTGeometryPath.createChildPath(k_Cell_Data), dataStructure, computedDCTGeometryPath.createChildPath(k_Cell_Data)); + UnitTest::CompareExemplarToGenerateAttributeMatrix(dataStructure, exemplarAbsorptionCTGeometryPath.createChildPath(k_Cell_Data), dataStructure, + computedAbsorptionCTGeometryPath.createChildPath(k_Cell_Data)); +} diff --git a/test/UnitTestCommon/include/simplnx/UnitTest/UnitTestCommon.hpp b/test/UnitTestCommon/include/simplnx/UnitTest/UnitTestCommon.hpp index a3576cc674..aaca3b8825 100644 --- a/test/UnitTestCommon/include/simplnx/UnitTest/UnitTestCommon.hpp +++ b/test/UnitTestCommon/include/simplnx/UnitTest/UnitTestCommon.hpp @@ -1020,6 +1020,95 @@ inline void AddImageGeometry(DataStructure& dataStructure, const SizeVec3& image imageGeom->setOrigin(origin); } +inline void CompareExemplarToGenerateAttributeMatrix(const DataStructure& exemplarDataStructure, const DataPath& exemplarAttributeMatrix, const DataStructure& computedDataStructure, + const DataPath& computedAttributeMatrix) +{ + auto& cellDataGroup = exemplarDataStructure.getDataRefAs(exemplarAttributeMatrix); + // std::vector selectedCellArrays; + + // Create the vector of all cell DataPaths from the exemplar data structure + for(auto& child : cellDataGroup) + { + DataPath exemplarDataArrayPath = exemplarAttributeMatrix.createChildPath(child.second->getName()); + DataPath computedDataArrayPath = computedAttributeMatrix.createChildPath(child.second->getName()); + + const auto* exemplarArrayPtr = exemplarDataStructure.getDataAs(exemplarDataArrayPath); + const auto* computedArrayPtr = computedDataStructure.getDataAs(computedDataArrayPath); + + // Check to see if there is something to compare against in the exemplar file. + if(nullptr == exemplarArrayPtr) + { + continue; + } + if(nullptr == computedArrayPtr) + { + continue; + } + + DataType type = DataType::int8; + const IArray::ArrayType arrayType = computedArrayPtr->getArrayType(); + if(arrayType == IArray::ArrayType::DataArray) + { + type = exemplarDataStructure.getDataRefAs(exemplarDataArrayPath).getDataType(); + } + if(arrayType == IArray::ArrayType::NeighborListArray) + { + type = exemplarDataStructure.getDataRefAs(exemplarDataArrayPath).getDataType(); + } + + switch(type) + { + case DataType::boolean: { + nx::core::UnitTest::CompareArrays(computedArrayPtr, exemplarArrayPtr); + break; + } + case DataType::int8: { + nx::core::UnitTest::CompareArrays(computedArrayPtr, exemplarArrayPtr); + break; + } + case DataType::int16: { + nx::core::UnitTest::CompareArrays(computedArrayPtr, exemplarArrayPtr); + break; + } + case DataType::int32: { + nx::core::UnitTest::CompareArrays(computedArrayPtr, exemplarArrayPtr); + break; + } + case DataType::int64: { + nx::core::UnitTest::CompareArrays(computedArrayPtr, exemplarArrayPtr); + break; + } + case DataType::uint8: { + nx::core::UnitTest::CompareArrays(computedArrayPtr, exemplarArrayPtr); + break; + } + case DataType::uint16: { + nx::core::UnitTest::CompareArrays(computedArrayPtr, exemplarArrayPtr); + break; + } + case DataType::uint32: { + nx::core::UnitTest::CompareArrays(computedArrayPtr, exemplarArrayPtr); + break; + } + case DataType::uint64: { + nx::core::UnitTest::CompareArrays(computedArrayPtr, exemplarArrayPtr); + break; + } + case DataType::float32: { + nx::core::UnitTest::CompareArrays(computedArrayPtr, exemplarArrayPtr); + break; + } + case DataType::float64: { + nx::core::UnitTest::CompareArrays(computedArrayPtr, exemplarArrayPtr); + break; + } + default: { + throw std::runtime_error("Invalid DataType"); + } + } + } +} + inline void CompareExemplarToGeneratedData(const DataStructure& dataStructure, const DataStructure& exemplarDataStructure, const DataPath attributeMatrix, const std::string& exemplarDataContainerName) { auto& cellDataGroup = dataStructure.getDataRefAs(attributeMatrix);