diff --git a/src/Plugins/OrientationAnalysis/CMakeLists.txt b/src/Plugins/OrientationAnalysis/CMakeLists.txt index db2d933d6f..d53126ab40 100644 --- a/src/Plugins/OrientationAnalysis/CMakeLists.txt +++ b/src/Plugins/OrientationAnalysis/CMakeLists.txt @@ -67,6 +67,7 @@ set(FilterList ReadH5EbsdFilter ReadH5EspritDataFilter ReadH5OimDataFilter + ReadH5OinaDataFilter RodriguesConvertorFilter RotateEulerRefFrameFilter WriteGBCDGMTFileFilter @@ -197,6 +198,7 @@ set(filter_algorithms ReadH5Ebsd ReadH5EspritData ReadH5OimData + ReadH5OinaData RodriguesConvertor RotateEulerRefFrame WriteGBCDGMTFile diff --git a/src/Plugins/OrientationAnalysis/docs/Images/ImportH5OinaFilter_1.png b/src/Plugins/OrientationAnalysis/docs/Images/ImportH5OinaFilter_1.png new file mode 100644 index 0000000000..eefb59821d Binary files /dev/null and b/src/Plugins/OrientationAnalysis/docs/Images/ImportH5OinaFilter_1.png differ diff --git a/src/Plugins/OrientationAnalysis/docs/ReadH5OinaDataFilter.md b/src/Plugins/OrientationAnalysis/docs/ReadH5OinaDataFilter.md new file mode 100644 index 0000000000..a1c33b96a6 --- /dev/null +++ b/src/Plugins/OrientationAnalysis/docs/ReadH5OinaDataFilter.md @@ -0,0 +1,96 @@ +# Import Oxford AZtec H5OINA Data File (.h5oina) + +## Group (Subgroup) + +Import/Export (Import) + +## Description + +This filter will read data from a single .h5oina 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**. + +### Limitations of the Filter + +The current implementation only understands the FORMAT VERSION 2.0 of the H5OINA file. This means that a user +can use a newer H5OINA file but the filter will only extract out the VERSION 2.0 headers and data. If the user +needs additional data from the file, the "Import HDF5 Dataset" filter can be used to agment this filter. + +![Overview of the user interface.](Images/ImportH5OinaFilter_1.png) + +## Notes About Reference Frames + +In order to bring the crystal reference frame and sample reference frame into coincidence, rotations **MAY** need to be applied to the data. There are 2 filters that can perform the necessary rotations. + ++ [Rotate Euler Reference Frame](../RotateEulerRefFrameFilter/index.html) ++ [Rotate Sample Reference Frame](../RotateSampleRefFrameFilter/index.html) + +Historical reference frame operations for a .ctf file are the following: + ++ 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. + +### Radians and Degrees + +All orientation data in the H5OINA file are 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** | + +## Parameters + +| Name | Type | Description | +|------|------| ----------- | +| Input File | File Path | The input .h5 file path | +| Scan Name | String | The name of the scan in the .h5oina file. | +| Z Spacing | float | The spacing in microns between each layer. | +| Origin | float (3x1) | The origin of the volume | +| Import Pattern Data | bool | Default=OFF | +| Hexagonal Axis Alignment | bool | Should the filter convert a Hexagonal phase to the EDAX standard for x-axis alignment | +| Convert Phase data to Int32 | bool | Should the phase data be converted to Int32 or keep the original uint8 | + +## Created Objects + +| Kind | Default Name | Type | Comp Dims | Description | +|-------------|--------------|------|-----------|----------------------------------------| +| Data Container | ImageDataContainer | N/A | N/A | Created Data Container name with an **Image Geometry** | +| Attribute Matrix | CellData | Cell | N/A | Created **Cell Attribute Matrix** name | +| Attribute Matrix | CellEnsembleData | Cell Ensemble | N/A | Created **Cell Ensemble Attribute Matrix** name | +| Cell Attribute Array | Band Contrast | uint8 | (1) | | +| Cell Attribute Array | Band Slope | uint8 | (1) | | +| Cell Attribute Array | Bands | uint8 | (1) | | +| Cell Attribute Array | Error | uint8 | (1) | The error descriptions are saved as attributes in the .h5oina file | +| Cell Attribute Array | Euler | float | (3) | Three angles defining the orientation of the **Cell** in Bunge convention (Z-X-Z) | +| Cell Attribute Array | MeanAngularDeviation | float | (1) | | +| Cell Attribute Array | Phase | uint8 | (1) | | +| Cell Attribute Array | X | float | (1) | The X Position of the scan point | +| Cell Attribute Array | Y | float | (1) | The Y Position of the scan point | +| Ensemble Attribute Array | CrystalStructures | uint32_t | (1) | Enumeration representing the crystal structure for each **Ensemble** | +| Ensemble Attribute Array | LatticeConstants | float | (6) | The 6 values that define the lattice constants for each **Ensemble**| +| Ensemble Attribute Array | MaterialName | String | (1) | Name of each **Ensemble** | + +## Example Pipelines + +## 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) + +## DREAM3DNX Help + +Check out our GitHub community page at [DREAM3DNX-Issues](https://github.com/BlueQuartzSoftware/DREAM3DNX-Issues/discussions) to report bugs, ask the community for help, discuss features, or get help from the developers. diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ReadH5OinaData.cpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ReadH5OinaData.cpp new file mode 100644 index 0000000000..6420eac84a --- /dev/null +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ReadH5OinaData.cpp @@ -0,0 +1,131 @@ +#include "ReadH5OinaData.hpp" + +#include "simplnx/DataStructure/DataArray.hpp" +#include "simplnx/DataStructure/Geometry/ImageGeom.hpp" + +using namespace nx::core; + +namespace +{ + +template +void copyRawData(const ReadH5DataInputValues* m_InputValues, size_t totalPoints, DataStructure& m_DataStructure, H5OINAReader& m_Reader, const std::string& name, usize offset) +{ + using ArrayType = DataArray; + auto& dataRef = m_DataStructure.getDataRefAs(m_InputValues->CellAttributeMatrixPath.createChildPath(name)); + auto* dataStorePtr = dataRef.getDataStore(); + + const nonstd::span rawDataPtr(reinterpret_cast(m_Reader.getPointerByName(name)), totalPoints); + std::copy(rawDataPtr.begin(), rawDataPtr.end(), dataStorePtr->begin() + offset); +} + +template +void convertHexEulerAngle(const ReadH5DataInputValues* m_InputValues, size_t totalPoints, DataStructure& m_DataStructure) +{ + using ArrayType = DataArray; + + if(m_InputValues->EdaxHexagonalAlignment) + { + auto& crystalStructuresRef = m_DataStructure.getDataRefAs(m_InputValues->CellEnsembleAttributeMatrixPath.createChildPath(EbsdLib::AngFile::CrystalStructures)); + auto& crystalStructuresDSRef = crystalStructuresRef.getDataStoreRef(); + + auto& cellPhasesRef = m_DataStructure.getDataRefAs(m_InputValues->CellAttributeMatrixPath.createChildPath(EbsdLib::H5OINA::Phase)); + auto& cellPhasesDSRef = cellPhasesRef.getDataStoreRef(); + + auto& eulerRef = m_DataStructure.getDataRefAs(m_InputValues->CellAttributeMatrixPath.createChildPath(EbsdLib::H5OINA::Euler)); + auto& eulerDataStoreRef = eulerRef.getDataStoreRef(); + + for(size_t i = 0; i < totalPoints; i++) + { + if(crystalStructuresDSRef[cellPhasesDSRef[i]] == EbsdLib::CrystalStructure::Hexagonal_High) + { + eulerDataStoreRef[3 * i + 2] = eulerDataStoreRef[3 * i + 2] + 30.0F; // See the documentation for this correction factor + } + } + } +} + +} // namespace + +// ----------------------------------------------------------------------------- +ReadH5OinaData::ReadH5OinaData(DataStructure& dataStructure, const IFilter::MessageHandler& mesgHandler, const std::atomic_bool& shouldCancel, ReadH5DataInputValues* inputValues) +: ReadH5Data(dataStructure, mesgHandler, shouldCancel, inputValues) +{ +} + +// ----------------------------------------------------------------------------- +ReadH5OinaData::~ReadH5OinaData() noexcept = default; + +// ----------------------------------------------------------------------------- +Result<> ReadH5OinaData::operator()() +{ + return execute(); +} + +// ----------------------------------------------------------------------------- +Result<> ReadH5OinaData::copyRawEbsdData(int index) +{ + const auto& imageGeom = m_DataStructure.getDataRefAs(m_InputValues->ImageGeometryPath); + const usize totalPoints = imageGeom.getNumXCells() * imageGeom.getNumYCells(); + const usize offset = index * totalPoints; + + copyRawData(m_InputValues, totalPoints, m_DataStructure, *m_Reader, EbsdLib::H5OINA::BandContrast, offset); + copyRawData(m_InputValues, totalPoints, m_DataStructure, *m_Reader, EbsdLib::H5OINA::BandSlope, offset); + copyRawData(m_InputValues, totalPoints, m_DataStructure, *m_Reader, EbsdLib::H5OINA::Bands, offset); + copyRawData(m_InputValues, totalPoints, m_DataStructure, *m_Reader, EbsdLib::H5OINA::Error, offset); + copyRawData(m_InputValues, totalPoints * 3, m_DataStructure, *m_Reader, EbsdLib::H5OINA::Euler, offset); + copyRawData(m_InputValues, totalPoints, m_DataStructure, *m_Reader, EbsdLib::H5OINA::MeanAngularDeviation, offset); + if(m_InputValues->ConvertPhaseToInt32) + { + const nonstd::span rawDataPtr(reinterpret_cast(m_Reader->getPointerByName(EbsdLib::H5OINA::Phase)), totalPoints); + using ArrayType = DataArray; + auto& dataRef = m_DataStructure.getDataRefAs(m_InputValues->CellAttributeMatrixPath.createChildPath(EbsdLib::H5OINA::Phase)); + auto* dataStorePtr = dataRef.getDataStore(); + for(size_t i = 0; i < totalPoints; i++) + { + dataStorePtr->setValue(i + offset, static_cast(rawDataPtr[i])); + } + } + else + { + copyRawData(m_InputValues, totalPoints, m_DataStructure, *m_Reader, EbsdLib::H5OINA::Phase, offset); + } + copyRawData(m_InputValues, totalPoints, m_DataStructure, *m_Reader, EbsdLib::H5OINA::X, offset); + copyRawData(m_InputValues, totalPoints, m_DataStructure, *m_Reader, EbsdLib::H5OINA::Y, offset); + + if(m_InputValues->EdaxHexagonalAlignment) + { + if(m_InputValues->ConvertPhaseToInt32) + { + convertHexEulerAngle(m_InputValues, totalPoints, m_DataStructure); + } + else + { + convertHexEulerAngle(m_InputValues, totalPoints, m_DataStructure); + } + } + + if(m_InputValues->ReadPatternData) + { + const uint16* patternDataPtr = m_Reader->getPatternData(); + std::array pDims = {{0, 0}}; + m_Reader->getPatternDims(pDims); + if(pDims[0] != 0 && pDims[1] != 0) + { + std::vector pDimsV(2); + pDimsV[0] = pDims[0]; + pDimsV[1] = pDims[1]; + auto& patternData = m_DataStructure.getDataRefAs(m_InputValues->CellAttributeMatrixPath.createChildPath(EbsdLib::H5OINA::UnprocessedPatterns)); + const usize numComponents = patternData.getNumberOfComponents(); + for(usize i = 0; i < totalPoints; i++) + { + for(usize j = 0; j < numComponents; ++j) + { + patternData[offset + numComponents * i + j] = patternDataPtr[numComponents * i + j]; + } + } + } + } + + return {}; +} diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ReadH5OinaData.hpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ReadH5OinaData.hpp new file mode 100644 index 0000000000..55a185fe9d --- /dev/null +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ReadH5OinaData.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include "OrientationAnalysis/OrientationAnalysis_export.hpp" +#include "OrientationAnalysis/utilities/ReadH5Data.hpp" + +namespace nx::core +{ + +/** + * @class ReadH5OinaData + * @brief This filter will read a single .h5 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 ReadH5OinaData : public ReadH5Data +{ +public: + ReadH5OinaData(DataStructure& dataStructure, const IFilter::MessageHandler& mesgHandler, const std::atomic_bool& shouldCancel, ReadH5DataInputValues* inputValues); + ~ReadH5OinaData() noexcept override; + + ReadH5OinaData(const ReadH5OinaData&) = delete; + ReadH5OinaData(ReadH5OinaData&&) noexcept = delete; + ReadH5OinaData& operator=(const ReadH5OinaData&) = delete; + ReadH5OinaData& operator=(ReadH5OinaData&&) noexcept = delete; + + Result<> operator()(); + + Result<> copyRawEbsdData(int index) override; +}; + +} // namespace nx::core diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/ReadH5OimDataFilter.cpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/ReadH5OimDataFilter.cpp index a1be9cd31b..ae5d0e1989 100644 --- a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/ReadH5OimDataFilter.cpp +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/ReadH5OimDataFilter.cpp @@ -65,9 +65,8 @@ Parameters ReadH5OimDataFilter::parameters() const // Create the parameter descriptors that are needed for this filter params.insertSeparator(Parameters::Separator{"Input Parameters"}); params.insert(std::make_unique(k_SelectedScanNames_Key, "Scan Names", "The name of the scan in the .h5 file. EDAX can store multiple scans in a single file", - OEMEbsdScanSelectionParameter::ValueType{}, - /* OEMEbsdScanSelectionParameter::AllowedManufacturers{EbsdLib::OEM::EDAX},*/ - OEMEbsdScanSelectionParameter::EbsdReaderType::Oim, OEMEbsdScanSelectionParameter::ExtensionsType{".h5"})); + OEMEbsdScanSelectionParameter::ValueType{}, OEMEbsdScanSelectionParameter::EbsdReaderType::Oim, + OEMEbsdScanSelectionParameter::ExtensionsType{".h5"})); params.insert(std::make_unique(k_ZSpacing_Key, "Z Spacing (Microns)", "The spacing in microns between each layer.", 1.0f)); params.insert(std::make_unique(k_Origin_Key, "Origin", "The origin of the volume", std::vector{0.0F, 0.0F, 0.0F}, std::vector{"x", "y", "z"})); params.insert(std::make_unique(k_ReadPatternData_Key, "Import Pattern Data", "Whether or not to import the pattern data", false)); diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/ReadH5OinaDataFilter.cpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/ReadH5OinaDataFilter.cpp new file mode 100644 index 0000000000..fe3d9fc96f --- /dev/null +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/ReadH5OinaDataFilter.cpp @@ -0,0 +1,211 @@ +#include "ReadH5OinaDataFilter.hpp" + +#include "OrientationAnalysis/Filters/Algorithms/ReadH5OinaData.hpp" +#include "OrientationAnalysis/Parameters/OEMEbsdScanSelectionParameter.h" + +#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 "simplnx/Parameters/NumberParameter.hpp" +#include "simplnx/Parameters/VectorParameter.hpp" + +#include "EbsdLib/IO/HKL/CtfFields.h" +#include "EbsdLib/IO/HKL/H5OINAReader.h" + +#include +namespace fs = std::filesystem; + +using namespace nx::core; + +namespace nx::core +{ +//------------------------------------------------------------------------------ +std::string ReadH5OinaDataFilter::name() const +{ + return FilterTraits::name.str(); +} + +//------------------------------------------------------------------------------ +std::string ReadH5OinaDataFilter::className() const +{ + return FilterTraits::className; +} + +//------------------------------------------------------------------------------ +Uuid ReadH5OinaDataFilter::uuid() const +{ + return FilterTraits::uuid; +} + +//------------------------------------------------------------------------------ +std::string ReadH5OinaDataFilter::humanName() const +{ + return "Read Oxford Aztec Data (.h5oina)"; +} + +//------------------------------------------------------------------------------ +std::vector ReadH5OinaDataFilter::defaultTags() const +{ + return {className(), "IO", "Input", "Read", "Import", "Oxford", "CTF", "H5OINA", "EBSD"}; +} + +//------------------------------------------------------------------------------ +Parameters ReadH5OinaDataFilter::parameters() const +{ + Parameters params; + + // Create the parameter descriptors that are needed for this filter + params.insertSeparator(Parameters::Separator{"Input Parameters"}); + params.insert(std::make_unique(k_SelectedScanNames_Key, "Scan Names", "The name of the scan in the .h5oina file. Oxford can store multiple scans in a single file", + OEMEbsdScanSelectionParameter::ValueType{}, OEMEbsdScanSelectionParameter::EbsdReaderType::H5Oina, + OEMEbsdScanSelectionParameter::ExtensionsType{".h5oina"})); + 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", true)); + params.insert(std::make_unique(k_ConvertPhaseToInt32_Key, "Convert Phase Data to Int32", "Native Phases data value is uint8. Convert to Int32 for better filter compatibility", true)); + params.insert(std::make_unique(k_ZSpacing_Key, "Z Spacing (Microns)", "The spacing in microns between each layer.", 1.0f)); + params.insert(std::make_unique(k_Origin_Key, "Origin", "The origin of the volume", std::vector{0.0F, 0.0F, 0.0F}, std::vector{"x", "y", "z"})); + params.insert(std::make_unique(k_ReadPatternData_Key, "Import Pattern Data", "Whether or not to import the pattern data", false)); + params.insert(std::make_unique(k_ImageGeometryName_Key, "Image Geometry", "The path to the created Image Geometry", DataPath({ImageGeom::k_TypeName}))); + params.insertSeparator(Parameters::Separator{"Cell Data"}); + 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{"Cell Ensemble Data"}); + params.insert(std::make_unique(k_CellEnsembleAttributeMatrixName_Key, "Cell Ensemble Attribute Matrix", + "The name of the cell ensemble data attribute matrix for the created Image Geometry", "Cell Ensemble Data")); + + return params; +} + +//------------------------------------------------------------------------------ +IFilter::UniquePointer ReadH5OinaDataFilter::clone() const +{ + return std::make_unique(); +} + +//------------------------------------------------------------------------------ +IFilter::PreflightResult ReadH5OinaDataFilter::preflightImpl(const DataStructure& dataStructure, const Arguments& filterArgs, const MessageHandler& messageHandler, + const std::atomic_bool& shouldCancel) const +{ + auto pSelectedScanNamesValue = filterArgs.value(k_SelectedScanNames_Key); + auto pZSpacingValue = filterArgs.value(k_ZSpacing_Key); + auto pOriginValue = filterArgs.value(k_Origin_Key); + auto pReadPatternDataValue = filterArgs.value(k_ReadPatternData_Key); + auto pImageGeometryNameValue = filterArgs.value(k_ImageGeometryName_Key); + auto pCellAttributeMatrixNameValue = filterArgs.value(k_CellAttributeMatrixName_Key); + auto pCellEnsembleAttributeMatrixNameValue = filterArgs.value(k_CellEnsembleAttributeMatrixName_Key); + auto pConvertPhaseData = filterArgs.value(k_ConvertPhaseToInt32_Key); + + DataPath cellEnsembleAMPath = pImageGeometryNameValue.createChildPath(pCellEnsembleAttributeMatrixNameValue); + DataPath cellAMPath = pImageGeometryNameValue.createChildPath(pCellAttributeMatrixNameValue); + + PreflightResult preflightResult; + nx::core::Result resultOutputActions; + std::vector preflightUpdatedValues; + + if(pZSpacingValue <= 0) + { + return MakePreflightErrorResult(-9580, "The Z Spacing field contains a value that is non-positive. The Z Spacing field must be set to a positive value."); + } + if(pSelectedScanNamesValue.scanNames.empty()) + { + return MakePreflightErrorResult(-9581, "At least one scan must be chosen. Please select a scan from the list."); + } + + // read in the necessary info from the input h5 file + H5OINAReader reader; + reader.setFileName(pSelectedScanNamesValue.inputFilePath.string()); + reader.setReadPatternData(pReadPatternDataValue); + reader.setHDF5Path(pSelectedScanNamesValue.scanNames.front()); + if(const int err = reader.readHeaderOnly(); err < 0) + { + return MakePreflightErrorResult(-9582, fmt::format("An error occurred while reading the header data\n{} : {}", err, reader.getErrorMessage())); + } + + // create the Image Geometry and it's attribute matrices + const CreateImageGeometryAction::DimensionType dims = {static_cast(reader.getXDimension()), static_cast(reader.getYDimension()), pSelectedScanNamesValue.scanNames.size()}; + const std::vector tupleDims = {dims[2], dims[1], dims[0]}; + { + CreateImageGeometryAction::SpacingType spacing = {reader.getXStep(), reader.getYStep(), pZSpacingValue}; + + auto createDataGroupAction = std::make_unique(pImageGeometryNameValue, dims, pOriginValue, spacing, pCellAttributeMatrixNameValue); + resultOutputActions.value().appendAction(std::move(createDataGroupAction)); + } + const auto phases = reader.getPhaseVector(); + 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(EbsdLib::CtfFile::CrystalStructures)); + resultOutputActions.value().appendAction(std::move(createArrayAction)); + } + { + auto createArrayAction = std::make_unique(DataType::float32, ensembleTupleDims, std::vector{6}, cellEnsembleAMPath.createChildPath(EbsdLib::CtfFile::LatticeConstants)); + resultOutputActions.value().appendAction(std::move(createArrayAction)); + } + { + auto createArrayAction = std::make_unique(ensembleTupleDims, cellEnsembleAMPath.createChildPath(EbsdLib::CtfFile::MaterialName)); + resultOutputActions.value().appendAction(std::move(createArrayAction)); + } + + // create the cell data arrays + resultOutputActions.value().appendAction(std::make_unique(DataType::uint8, tupleDims, std::vector{1}, cellAMPath.createChildPath(EbsdLib::H5OINA::BandContrast))); + resultOutputActions.value().appendAction(std::make_unique(DataType::uint8, tupleDims, std::vector{1}, cellAMPath.createChildPath(EbsdLib::H5OINA::BandSlope))); + resultOutputActions.value().appendAction(std::make_unique(DataType::uint8, tupleDims, std::vector{1}, cellAMPath.createChildPath(EbsdLib::H5OINA::Bands))); + resultOutputActions.value().appendAction(std::make_unique(DataType::uint8, tupleDims, std::vector{1}, cellAMPath.createChildPath(EbsdLib::H5OINA::Error))); + resultOutputActions.value().appendAction(std::make_unique(DataType::float32, tupleDims, std::vector{3}, cellAMPath.createChildPath(EbsdLib::H5OINA::Euler))); + resultOutputActions.value().appendAction(std::make_unique(DataType::float32, tupleDims, std::vector{1}, cellAMPath.createChildPath(EbsdLib::H5OINA::MeanAngularDeviation))); + if(pConvertPhaseData) + { + resultOutputActions.value().appendAction(std::make_unique(DataType::int32, tupleDims, std::vector{1}, cellAMPath.createChildPath(EbsdLib::H5OINA::Phase))); + } + else + { + resultOutputActions.value().appendAction(std::make_unique(DataType::uint8, tupleDims, std::vector{1}, cellAMPath.createChildPath(EbsdLib::H5OINA::Phase))); + } + resultOutputActions.value().appendAction(std::make_unique(DataType::float32, tupleDims, std::vector{1}, cellAMPath.createChildPath(EbsdLib::H5OINA::X))); + resultOutputActions.value().appendAction(std::make_unique(DataType::float32, tupleDims, std::vector{1}, cellAMPath.createChildPath(EbsdLib::H5OINA::Y))); + + if(pReadPatternDataValue) + { + std::array patternDims = {{0, 0}}; + reader.getPatternDims(patternDims); + if(patternDims[0] == 0 || patternDims[1] == 0) + { + return MakePreflightErrorResult(-9583, fmt::format("The parameter 'Read Pattern Data' has been enabled but there does not seem to be any pattern data in the file for the scan name selected")); + } + auto createArrayAction = std::make_unique(DataType::uint16, tupleDims, std::vector{static_cast(patternDims[0]), static_cast(patternDims[1])}, + cellAMPath.createChildPath(EbsdLib::H5OINA::UnprocessedPatterns)); + resultOutputActions.value().appendAction(std::move(createArrayAction)); + } + + return {std::move(resultOutputActions), std::move(preflightUpdatedValues)}; +} + +//------------------------------------------------------------------------------ +Result<> ReadH5OinaDataFilter::executeImpl(DataStructure& dataStructure, const Arguments& filterArgs, const PipelineFilter* pipelineNode, const MessageHandler& messageHandler, + const std::atomic_bool& shouldCancel) const +{ + ReadH5DataInputValues inputValues; + + inputValues.SelectedScanNames = filterArgs.value(k_SelectedScanNames_Key); + inputValues.ReadPatternData = filterArgs.value(k_ReadPatternData_Key); + inputValues.ImageGeometryPath = filterArgs.value(k_ImageGeometryName_Key); + inputValues.CellEnsembleAttributeMatrixPath = inputValues.ImageGeometryPath.createChildPath(filterArgs.value(k_CellEnsembleAttributeMatrixName_Key)); + inputValues.CellAttributeMatrixPath = inputValues.ImageGeometryPath.createChildPath(filterArgs.value(k_CellAttributeMatrixName_Key)); + inputValues.ConvertPhaseToInt32 = filterArgs.value(k_ConvertPhaseToInt32_Key); + inputValues.EdaxHexagonalAlignment = filterArgs.value(k_EdaxHexagonalAlignment_Key); + + return ReadH5OinaData(dataStructure, messageHandler, shouldCancel, &inputValues)(); +} +} // namespace nx::core diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/ReadH5OinaDataFilter.hpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/ReadH5OinaDataFilter.hpp new file mode 100644 index 0000000000..178e7a0bdb --- /dev/null +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/ReadH5OinaDataFilter.hpp @@ -0,0 +1,104 @@ +#pragma once + +#include "OrientationAnalysis/OrientationAnalysis_export.hpp" + +#include "simplnx/Filter/FilterTraits.hpp" +#include "simplnx/Filter/IFilter.hpp" + +namespace nx::core +{ +/** + * @class ReadH5OinaDataFilter + * @brief This filter will read a single .h5oina file into a new Image Geometry, allowing the immediate use of Filters on the data. + */ +class ORIENTATIONANALYSIS_EXPORT ReadH5OinaDataFilter : public IFilter +{ +public: + ReadH5OinaDataFilter() = default; + ~ReadH5OinaDataFilter() noexcept override = default; + + ReadH5OinaDataFilter(const ReadH5OinaDataFilter&) = delete; + ReadH5OinaDataFilter(ReadH5OinaDataFilter&&) noexcept = delete; + + ReadH5OinaDataFilter& operator=(const ReadH5OinaDataFilter&) = delete; + ReadH5OinaDataFilter& operator=(ReadH5OinaDataFilter&&) noexcept = delete; + + // Parameter Keys + static inline constexpr StringLiteral k_SelectedScanNames_Key = "selected_scan_names"; + static inline constexpr StringLiteral k_ZSpacing_Key = "z_spacing"; + static inline constexpr StringLiteral k_Origin_Key = "origin"; + static inline constexpr StringLiteral k_ReadPatternData_Key = "read_pattern_data"; + static inline constexpr StringLiteral k_ImageGeometryName_Key = "image_geometry_name"; + 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_EdaxHexagonalAlignment_Key = "edax_hexagonal_alignment"; + static inline constexpr StringLiteral k_ConvertPhaseToInt32_Key = "convert_phase_to_int32"; + + /** + * @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 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 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& data, 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, ReadH5OinaDataFilter, "fad3d47f-f1e1-4429-bc65-5e021be62ba0"); +/* LEGACY UUID FOR THIS FILTER 3ff4701b-3a0c-52e3-910a-fa927aa6584c */ diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/utilities/ReadH5Data.hpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/utilities/ReadH5Data.hpp index a71544659d..01574e602e 100644 --- a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/utilities/ReadH5Data.hpp +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/utilities/ReadH5Data.hpp @@ -9,6 +9,7 @@ #include "simplnx/Filter/IFilter.hpp" #include "EbsdLib/IO/BrukerNano/H5EspritReader.h" +#include "EbsdLib/IO/HKL/H5OINAReader.h" #include "EbsdLib/IO/TSL/H5OIMReader.h" namespace nx::core diff --git a/src/Plugins/OrientationAnalysis/test/CMakeLists.txt b/src/Plugins/OrientationAnalysis/test/CMakeLists.txt index 676fb97a9d..b0ea154fd1 100644 --- a/src/Plugins/OrientationAnalysis/test/CMakeLists.txt +++ b/src/Plugins/OrientationAnalysis/test/CMakeLists.txt @@ -46,6 +46,7 @@ set(${PLUGIN_NAME}UnitTest_SRCS ReadH5EbsdTest.cpp ReadH5EspritDataTest.cpp ReadH5OimDataTest.cpp + ReadH5OinaDataTest.cpp RodriguesConvertorTest.cpp RotateEulerRefFrameTest.cpp WriteGBCDGMTFileTest.cpp @@ -137,6 +138,7 @@ if(EXISTS "${DREAM3D_DATA_DIR}" AND SIMPLNX_DOWNLOAD_TEST_FILES) 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 INL_writer.tar.gz SHA512 7d723351e51e84540abfbc38e69a6014852ba34808f9d216a27063a616bcfbd5eb708405305fd83334e48c9ca133d3d0be797c05040e4a115cc612e385d9ada6) download_test_data(DREAM3D_DATA_DIR ${DREAM3D_DATA_DIR} ARCHIVE_NAME convert_hex_grid_to_square_grid_test.tar.gz SHA512 bb672ebbe2540ba493ad95bea95dac1f85b5634ac3311b5aa774ce3d2177103d1b45a13225221993dd40f0cbe02daf20ccd209d4ae0cab0bf034d97c5b234ba4) + download_test_data(DREAM3D_DATA_DIR ${DREAM3D_DATA_DIR} ARCHIVE_NAME H5Oina_Test_Data.tar.gz SHA512 346573ac6b96983680078e8b0a401aa25bd9302dff382ca86ae4e503ded6db3947c4c5611ee603db519d8a8dc6ed35b044a7bfea9880fade5ab54479d140ea03 ) endif() diff --git a/src/Plugins/OrientationAnalysis/test/ReadH5OinaDataTest.cpp b/src/Plugins/OrientationAnalysis/test/ReadH5OinaDataTest.cpp new file mode 100644 index 0000000000..83fc9e45a8 --- /dev/null +++ b/src/Plugins/OrientationAnalysis/test/ReadH5OinaDataTest.cpp @@ -0,0 +1,130 @@ +#include + +#include "simplnx/DataStructure/Geometry/ImageGeom.hpp" +#include "simplnx/Parameters/BoolParameter.hpp" +#include "simplnx/Parameters/DataGroupCreationParameter.hpp" +#include "simplnx/Parameters/VectorParameter.hpp" +#include "simplnx/UnitTest/UnitTestCommon.hpp" + +#include "OrientationAnalysis/Filters/ReadH5OinaDataFilter.hpp" +#include "OrientationAnalysis/OrientationAnalysis_test_dirs.hpp" +#include "OrientationAnalysis/Parameters/OEMEbsdScanSelectionParameter.h" + +#include "EbsdLib/IO/TSL/AngFields.h" + +#include +namespace fs = std::filesystem; + +using namespace nx::core; +using namespace nx::core::Constants; + +namespace +{ +const std::string k_ScanName = "1"; +} + +TEST_CASE("OrientationAnalysis::ReadH5OinaDataFilter: Valid Filter Execution", "[OrientationAnalysis][ReadH5OinaDataFilter]") +{ + Application::GetOrCreateInstance()->loadPlugins(unit_test::k_BuildDir.view(), true); + + const nx::core::UnitTest::TestFileSentinel testDataSentinel(nx::core::unit_test::k_CMakeExecutable, nx::core::unit_test::k_TestFilesDir, "H5Oina_Test_Data.tar.gz", "H5Oina_Test_Data"); + + // Read Exemplar DREAM3D File + auto exemplarFilePath = fs::path(fmt::format("{}/H5Oina_Test_Data/H5Oina_Test_Data.dream3d", unit_test::k_TestFilesDir)); + DataStructure exemplarDataStructure = UnitTest::LoadDataStructure(exemplarFilePath); + + // Instantiate the filter, a DataStructure object and an Arguments Object + ReadH5OinaDataFilter filter; + DataStructure dataStructure; + Arguments args; + + auto h5TestFile = fs::path(fmt::format("{}/H5Oina_Test_Data/H5Oina_Test_Data.h5oina", unit_test::k_TestFilesDir)); + OEMEbsdScanSelectionParameter::ValueType scanSelections = {h5TestFile, EbsdLib::RefFrameZDir::LowtoHigh, {k_ScanName}}; + + // Create default Parameters for the filter. + args.insertOrAssign(ReadH5OinaDataFilter::k_SelectedScanNames_Key, std::make_any(scanSelections)); + args.insertOrAssign(ReadH5OinaDataFilter::k_ZSpacing_Key, std::make_any(1.0f)); + args.insertOrAssign(ReadH5OinaDataFilter::k_Origin_Key, std::make_any(std::vector(3, 0.0f))); + args.insertOrAssign(ReadH5OinaDataFilter::k_ReadPatternData_Key, std::make_any(false)); + args.insertOrAssign(ReadH5OinaDataFilter::k_ImageGeometryName_Key, std::make_any(DataPath({ImageGeom::k_TypeName}))); + args.insertOrAssign(ReadH5OinaDataFilter::k_CellAttributeMatrixName_Key, std::make_any(k_CellData)); + args.insertOrAssign(ReadH5OinaDataFilter::k_CellEnsembleAttributeMatrixName_Key, std::make_any(k_CellEnsembleData)); + + // 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) + + const auto& imageGeom = dataStructure.getDataRefAs(DataPath({ImageGeom::k_TypeName})); + const auto& exemplarImageGeom = exemplarDataStructure.getDataRefAs(DataPath({k_ExemplarDataContainer})); + REQUIRE(imageGeom.getDimensions() == exemplarImageGeom.getDimensions()); + REQUIRE(imageGeom.getSpacing() == exemplarImageGeom.getSpacing()); + REQUIRE(imageGeom.getOrigin() == exemplarImageGeom.getOrigin()); + REQUIRE(imageGeom.getUnits() == IGeometry::LengthUnit::Micrometer); + + UnitTest::CompareExemplarToGeneratedData(dataStructure, exemplarDataStructure, DataPath({ImageGeom::k_TypeName, k_CellData}), k_ExemplarDataContainer); + + const DataPath cellEnsemblePath({ImageGeom::k_TypeName, k_CellEnsembleData}); + const DataPath exemplarCellEnsemblePath({k_ExemplarDataContainer, k_CellEnsembleData}); + const auto& crystalStructures = dataStructure.getDataRefAs(cellEnsemblePath.createChildPath(EbsdLib::AngFile::CrystalStructures)); + const auto& crystalStructuresExemplar = exemplarDataStructure.getDataRefAs(exemplarCellEnsemblePath.createChildPath(EbsdLib::AngFile::CrystalStructures)); + UnitTest::CompareDataArrays(crystalStructures, crystalStructuresExemplar); + const auto& latticeConstants = dataStructure.getDataRefAs(cellEnsemblePath.createChildPath(EbsdLib::AngFile::LatticeConstants)); + const auto& latticeConstantsExemplar = exemplarDataStructure.getDataRefAs(exemplarCellEnsemblePath.createChildPath(EbsdLib::AngFile::LatticeConstants)); + UnitTest::CompareDataArrays(latticeConstants, latticeConstantsExemplar); + const auto& materialName = dataStructure.getDataRefAs(cellEnsemblePath.createChildPath(EbsdLib::AngFile::MaterialName)); + const auto& materialNameExemplar = exemplarDataStructure.getDataRefAs(exemplarCellEnsemblePath.createChildPath(EbsdLib::AngFile::MaterialName)); + UnitTest::CompareStringArrays(materialNameExemplar, materialName); +} + +TEST_CASE("OrientationAnalysis::ReadH5OinaDataFilter: InValid Filter Execution", "[OrientationAnalysis][ReadH5OinaDataFilter]") +{ + Application::GetOrCreateInstance()->loadPlugins(unit_test::k_BuildDir.view(), true); + + const nx::core::UnitTest::TestFileSentinel testDataSentinel(nx::core::unit_test::k_CMakeExecutable, nx::core::unit_test::k_TestFilesDir, "6_6_ImportH5Data.tar.gz", "6_6_ImportH5Data"); + + // Instantiate the filter, a DataStructure object and an Arguments Object + ReadH5OinaDataFilter filter; + DataStructure dataStructure; + Arguments args; + args.insertOrAssign(ReadH5OinaDataFilter::k_Origin_Key, std::make_any(std::vector(3, 0.0f))); + args.insertOrAssign(ReadH5OinaDataFilter::k_ImageGeometryName_Key, std::make_any(DataPath({ImageGeom::k_TypeName}))); + args.insertOrAssign(ReadH5OinaDataFilter::k_CellAttributeMatrixName_Key, std::make_any(k_CellData)); + args.insertOrAssign(ReadH5OinaDataFilter::k_CellEnsembleAttributeMatrixName_Key, std::make_any(k_CellEnsembleData)); + + auto h5TestFile = fs::path(fmt::format("{}/H5Oina_Test_Data/FirstLook AB Site 1 Map Data 4.h5oina", unit_test::k_TestFilesDir)); + OEMEbsdScanSelectionParameter::ValueType scanSelections = {h5TestFile, EbsdLib::RefFrameZDir::LowtoHigh, {k_ScanName}}; + + SECTION("Invalid Z Spacing") + { + args.insertOrAssign(ReadH5OinaDataFilter::k_SelectedScanNames_Key, std::make_any(scanSelections)); + args.insertOrAssign(ReadH5OinaDataFilter::k_ZSpacing_Key, std::make_any(0.0f)); + args.insertOrAssign(ReadH5OinaDataFilter::k_ReadPatternData_Key, std::make_any(false)); + } + SECTION("No Scan Names Selected") + { + scanSelections.scanNames.clear(); + args.insertOrAssign(ReadH5OinaDataFilter::k_SelectedScanNames_Key, std::make_any(scanSelections)); + args.insertOrAssign(ReadH5OinaDataFilter::k_ZSpacing_Key, std::make_any(1.0f)); + args.insertOrAssign(ReadH5OinaDataFilter::k_ReadPatternData_Key, std::make_any(false)); + } + SECTION("Invalid h5 file type (incompatible manufacturer)") + { + h5TestFile = fs::path(fmt::format("{}/H5Oina_Test_Data/FirstLook AB Site 1 Map Data 4.h5oina", unit_test::k_TestFilesDir)); + scanSelections.inputFilePath = h5TestFile; + args.insertOrAssign(ReadH5OinaDataFilter::k_SelectedScanNames_Key, std::make_any(scanSelections)); + args.insertOrAssign(ReadH5OinaDataFilter::k_ZSpacing_Key, std::make_any(1.0f)); + args.insertOrAssign(ReadH5OinaDataFilter::k_ReadPatternData_Key, std::make_any(false)); + } + + // Preflight the filter and check result + auto preflightResult = filter.preflight(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_INVALID(preflightResult.outputActions) + + // Execute the filter and check the result + auto executeResult = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_INVALID(executeResult.result) +}