From 3a1e77e8b36a163ead059cebe415fb56749533ce Mon Sep 17 00:00:00 2001 From: Michael Jackson Date: Wed, 7 Feb 2024 12:38:06 -0500 Subject: [PATCH] BUG: GeneratePoleFigure-Only create RGB when creating the Image Geometry (#827) Signed-off-by: Michael Jackson --- .../docs/WritePoleFigureFilter.md | 21 ++++++-- .../Filters/Algorithms/WritePoleFigure.cpp | 46 ++++++++++------ .../test/WritePoleFigureTest.cpp | 53 ++++++++++++++++--- 3 files changed, 94 insertions(+), 26 deletions(-) diff --git a/src/Plugins/OrientationAnalysis/docs/WritePoleFigureFilter.md b/src/Plugins/OrientationAnalysis/docs/WritePoleFigureFilter.md index 36554c8f68..b0ca5ae9a7 100644 --- a/src/Plugins/OrientationAnalysis/docs/WritePoleFigureFilter.md +++ b/src/Plugins/OrientationAnalysis/docs/WritePoleFigureFilter.md @@ -6,7 +6,20 @@ IO (Output) ## Description -This **Filter** creates a standard pole figure image for each **Ensemble** in a selected **Data Container** with an **Image Geometry**. The **Filter** uses Euler angles in radians and requires the crystal structures for each **Ensemble** array and the corresponding **Ensemble** Ids on the **Cells**. The **Filter** also optionally can use a *mask* array to determine which **Cells** are valid for the pole figure computation. +This **Filter** creates a standard crystallographic pole figure image for each **Ensemble** (phase)in a selected **Data Container**. The **Filter** uses Euler angles in radians and requires the crystal structures and material names for each **Ensemble** array and the corresponding **Ensemble** Ids on the **Cells**. The **Filter** also optionally can use a *mask* array to determine which **Cells** are valid for the pole figure computation. + +In a practicale sense, this means that the following information is available to the filter: + +- Cell Level + + - Euler Angles (Float 32) ordered as sets of (phi1, Phi, phi2). + - Phases (Int32) This is the phase that each Euler angle belongs to + - Optional Mask(boolean or uint8) True/1 if the Euler angle should be included in the pole figure. + +- Ensemble Level (Phase Information) + + - Laue Class (UInt32) + - Material Names (String) ### Algorithm Choice @@ -18,7 +31,7 @@ This **Filter** creates a standard pole figure image for each **Ensemble** in a ### Layout -The 3 pole figures can be laid out in a Square, Horizontal row or vertical column. Supporting informatio (including the color bar legend for color pole figures) will also be printed on the image. +The 3 pole figures can be laid out in a Square, Horizontal row or vertical column. Supporting information (including the color bar legend for color pole figures) will also be printed on the image. | Lambert Projection | Discrete | |--------------------|----------| @@ -28,8 +41,8 @@ The 3 pole figures can be laid out in a Square, Horizontal row or vertical colum ## Example Pipelines -+ TxCopper_Exposed -+ TxCopper_Unexposed +- TxCopper_Exposed +- TxCopper_Unexposed ## License & Copyright diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/WritePoleFigure.cpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/WritePoleFigure.cpp index b86321b36f..72c9b5625b 100644 --- a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/WritePoleFigure.cpp +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/WritePoleFigure.cpp @@ -362,21 +362,14 @@ Result<> WritePoleFigure::operator()() // Find how many phases we have by getting the number of Crystal Structures const size_t numPhases = crystalStructures.getNumberOfTuples(); - // Loop over all the voxels gathering the Eulers for a specific phase into an array + // Create the Image Geometry that will serve as the final storage location for each + // pole figure. We are just giving it a default size for now, it will be resized + // further down the algorithm. std::vector tupleShape = {1, static_cast(m_InputValues->ImageSize), static_cast(m_InputValues->ImageSize)}; auto& imageGeom = m_DataStructure.getDataRefAs(m_InputValues->OutputImageGeometryPath); auto cellAttrMatPath = imageGeom.getCellDataPath(); imageGeom.setDimensions({static_cast(m_InputValues->ImageSize), static_cast(m_InputValues->ImageSize), 1}); imageGeom.getCellData()->resizeTuples(tupleShape); - for(size_t phase = 1; phase < numPhases; ++phase) - { - auto imageArrayPath = cellAttrMatPath.createChildPath(fmt::format("{}Phase_{}", m_InputValues->ImagePrefix, phase)); - auto arrayCreationResult = nx::core::CreateArray(m_DataStructure, tupleShape, {4ULL}, imageArrayPath, IDataAction::Mode::Execute); - if(arrayCreationResult.invalid()) - { - return arrayCreationResult; - } - } // Loop over all the voxels gathering the Eulers for a specific phase into an array for(size_t phase = 1; phase < numPhases; ++phase) @@ -666,22 +659,43 @@ Result<> WritePoleFigure::operator()() } // Fetch the rendered RGBA pixels from the entire canvas. - std::vector image(static_cast(pageHeight * pageWidth * 4)); - context.get_image_data(image.data(), pageWidth, pageHeight, pageWidth * 4, 0, 0); + std::vector rgbaCanvasImage(static_cast(pageHeight * pageWidth * 4)); + context.get_image_data(rgbaCanvasImage.data(), pageWidth, pageHeight, pageWidth * 4, 0, 0); if(m_InputValues->SaveAsImageGeometry) { + // Ensure the final Image Geometry is sized correctly. imageGeom.setDimensions({static_cast(pageWidth), static_cast(pageHeight), 1}); imageGeom.getCellData()->resizeTuples({1, static_cast(pageHeight), static_cast(pageWidth)}); + tupleShape[0] = 1; + tupleShape[1] = pageHeight; + tupleShape[2] = pageWidth; + // Create an output array to hold the RGB formatted color image + auto imageArrayPath = cellAttrMatPath.createChildPath(fmt::format("{}{}", m_InputValues->ImagePrefix, phase)); + auto arrayCreationResult = nx::core::CreateArray(m_DataStructure, tupleShape, {3ULL}, imageArrayPath, IDataAction::Mode::Execute); + if(arrayCreationResult.invalid()) + { + return arrayCreationResult; + } - auto imageArrayPath = cellAttrMatPath.createChildPath(fmt::format("{}Phase_{}", m_InputValues->ImagePrefix, phase)); + // Get a reference to the RGB final array and then copy ONLY the RGB pixels from the + // canvas RGBA data. auto& imageData = m_DataStructure.getDataRefAs(imageArrayPath); - std::copy(image.begin(), image.end(), imageData.begin()); + + imageData.fill(0); + size_t tupleCount = pageHeight * pageWidth; + for(size_t t = 0; t < tupleCount; t++) + { + imageData[t * 3 + 0] = rgbaCanvasImage[t * 4 + 0]; + imageData[t * 3 + 1] = rgbaCanvasImage[t * 4 + 1]; + imageData[t * 3 + 2] = rgbaCanvasImage[t * 4 + 2]; + } } + // Write out the full RGBA data if(m_InputValues->WriteImageToDisk) { - const std::string filename = fmt::format("{}/{}Phase_{}.tiff", m_InputValues->OutputPath.string(), m_InputValues->ImagePrefix, phase); - auto result = TiffWriter::WriteImage(filename, pageWidth, pageHeight, 4, image.data()); + const std::string filename = fmt::format("{}/{}{}.tiff", m_InputValues->OutputPath.string(), m_InputValues->ImagePrefix, phase); + auto result = TiffWriter::WriteImage(filename, pageWidth, pageHeight, 4, rgbaCanvasImage.data()); if(result.first < 0) { return MakeErrorResult(-53900, fmt::format("Error writing pole figure image '{}' to disk.\n Error Code from Tiff Writer: {}\n Message: {}", filename, result.first, result.second)); diff --git a/src/Plugins/OrientationAnalysis/test/WritePoleFigureTest.cpp b/src/Plugins/OrientationAnalysis/test/WritePoleFigureTest.cpp index 76e48687f7..f3126ef256 100644 --- a/src/Plugins/OrientationAnalysis/test/WritePoleFigureTest.cpp +++ b/src/Plugins/OrientationAnalysis/test/WritePoleFigureTest.cpp @@ -21,8 +21,43 @@ using namespace nx::core::UnitTest; namespace { const std::string k_ImagePrefix("fw-ar-IF1-aptr12-corr Discrete Pole Figure"); + +template +void CompareComponentsOfArrays(const DataStructure& dataStructure, const DataPath& exemplaryDataPath, const DataPath& computedPath, usize compIndex) +{ + // DataPath exemplaryDataPath = featureGroup.createChildPath("SurfaceFeatures"); + REQUIRE_NOTHROW(dataStructure.getDataRefAs>(exemplaryDataPath)); + REQUIRE_NOTHROW(dataStructure.getDataRefAs>(computedPath)); + + const auto& exemplaryDataArray = dataStructure.getDataRefAs>(exemplaryDataPath); + const auto& generatedDataArray = dataStructure.getDataRefAs>(computedPath); + REQUIRE(generatedDataArray.getNumberOfTuples() == exemplaryDataArray.getNumberOfTuples()); + + usize exemplaryNumComp = exemplaryDataArray.getNumberOfComponents(); + usize generatedNumComp = generatedDataArray.getNumberOfComponents(); + + REQUIRE(exemplaryNumComp == 4); + REQUIRE(generatedNumComp == 3); + + REQUIRE(compIndex < exemplaryNumComp); + REQUIRE(compIndex < generatedNumComp); + + INFO(fmt::format("Bad Comparison\n Input Data Array:'{}'\n Output DataArray: '{}'", exemplaryDataPath.toString(), computedPath.toString())); + + usize start = 0; + usize numTuples = exemplaryDataArray.getNumberOfTuples(); + for(usize i = start; i < numTuples; i++) + { + auto oldVal = exemplaryDataArray[i * exemplaryNumComp + compIndex]; + auto newVal = generatedDataArray[i * generatedNumComp + compIndex]; + INFO(fmt::format("Index: {} Comp: {}", i, compIndex)); + + REQUIRE(oldVal == newVal); + } } +} // namespace + TEST_CASE("OrientationAnalysis::WritePoleFigureFilter-1", "[OrientationAnalysis][WritePoleFigureFilter]") { Application::GetOrCreateInstance()->loadPlugins(unit_test::k_BuildDir.view(), true); @@ -51,7 +86,7 @@ TEST_CASE("OrientationAnalysis::WritePoleFigureFilter-1", "[OrientationAnalysis] args.insertOrAssign(WritePoleFigureFilter::k_UseMask_Key, std::make_any(false)); args.insertOrAssign(WritePoleFigureFilter::k_ImageGeometryPath_Key, std::make_any(DataPath({"fw-ar-IF1-aptr12-corr Discrete Pole Figure [CALCULATED]"}))); - DataPath calculatedImageData({"fw-ar-IF1-aptr12-corr Discrete Pole Figure [CALCULATED]", "CellData", fmt::format("{}Phase_{}", k_ImagePrefix, 1)}); + DataPath calculatedImageData({"fw-ar-IF1-aptr12-corr Discrete Pole Figure [CALCULATED]", "CellData", fmt::format("{}{}", k_ImagePrefix, 1)}); DataPath exemplarImageData({"fw-ar-IF1-aptr12-corr Discrete Pole Figure", "CellData", "Image"}); args.insertOrAssign(WritePoleFigureFilter::k_CellEulerAnglesArrayPath_Key, std::make_any(DataPath({"fw-ar-IF1-aptr12-corr", "Cell Data", "EulerAngles"}))); @@ -71,7 +106,9 @@ TEST_CASE("OrientationAnalysis::WritePoleFigureFilter-1", "[OrientationAnalysis] WriteTestDataStructure(dataStructure, fmt::format("{}/write_pole_figure-1.dream3d", unit_test::k_BinaryTestOutputDir)); #endif - CompareArrays(dataStructure, exemplarImageData, calculatedImageData); + CompareComponentsOfArrays(dataStructure, exemplarImageData, calculatedImageData, 0); + CompareComponentsOfArrays(dataStructure, exemplarImageData, calculatedImageData, 1); + CompareComponentsOfArrays(dataStructure, exemplarImageData, calculatedImageData, 2); } TEST_CASE("OrientationAnalysis::WritePoleFigureFilter-2", "[OrientationAnalysis][WritePoleFigureFilter]") @@ -102,7 +139,7 @@ TEST_CASE("OrientationAnalysis::WritePoleFigureFilter-2", "[OrientationAnalysis] args.insertOrAssign(WritePoleFigureFilter::k_UseMask_Key, std::make_any(true)); args.insertOrAssign(WritePoleFigureFilter::k_ImageGeometryPath_Key, std::make_any(DataPath({"fw-ar-IF1-aptr12-corr Discrete Pole Figure Masked [CALCULATED]"}))); - DataPath calculatedImageData({"fw-ar-IF1-aptr12-corr Discrete Pole Figure Masked [CALCULATED]", "CellData", fmt::format("{}Phase_{}", k_ImagePrefix, 1)}); + DataPath calculatedImageData({"fw-ar-IF1-aptr12-corr Discrete Pole Figure Masked [CALCULATED]", "CellData", fmt::format("{}{}", k_ImagePrefix, 1)}); DataPath exemplarImageData({"fw-ar-IF1-aptr12-corr Discrete Pole Figure Masked", "CellData", "Image"}); args.insertOrAssign(WritePoleFigureFilter::k_CellEulerAnglesArrayPath_Key, std::make_any(DataPath({"fw-ar-IF1-aptr12-corr", "Cell Data", "EulerAngles"}))); @@ -123,7 +160,9 @@ TEST_CASE("OrientationAnalysis::WritePoleFigureFilter-2", "[OrientationAnalysis] WriteTestDataStructure(dataStructure, fmt::format("{}/write_pole_figure-2.dream3d", unit_test::k_BinaryTestOutputDir)); #endif - CompareArrays(dataStructure, exemplarImageData, calculatedImageData); + CompareComponentsOfArrays(dataStructure, exemplarImageData, calculatedImageData, 0); + CompareComponentsOfArrays(dataStructure, exemplarImageData, calculatedImageData, 1); + CompareComponentsOfArrays(dataStructure, exemplarImageData, calculatedImageData, 2); } TEST_CASE("OrientationAnalysis::WritePoleFigureFilter-3", "[OrientationAnalysis][WritePoleFigureFilter]") @@ -154,7 +193,7 @@ TEST_CASE("OrientationAnalysis::WritePoleFigureFilter-3", "[OrientationAnalysis] args.insertOrAssign(WritePoleFigureFilter::k_UseMask_Key, std::make_any(true)); args.insertOrAssign(WritePoleFigureFilter::k_ImageGeometryPath_Key, std::make_any(DataPath({"fw-ar-IF1-aptr12-corr Discrete Pole Figure Masked Color [CALCULATED]"}))); - DataPath calculatedImageData({"fw-ar-IF1-aptr12-corr Discrete Pole Figure Masked Color [CALCULATED]", "CellData", fmt::format("{}Phase_{}", k_ImagePrefix, 1)}); + DataPath calculatedImageData({"fw-ar-IF1-aptr12-corr Discrete Pole Figure Masked Color [CALCULATED]", "CellData", fmt::format("{}{}", k_ImagePrefix, 1)}); DataPath exemplarImageData({"fw-ar-IF1-aptr12-corr Discrete Pole Figure Masked Color", "CellData", "Image"}); args.insertOrAssign(WritePoleFigureFilter::k_CellEulerAnglesArrayPath_Key, std::make_any(DataPath({"fw-ar-IF1-aptr12-corr", "Cell Data", "EulerAngles"}))); @@ -175,5 +214,7 @@ TEST_CASE("OrientationAnalysis::WritePoleFigureFilter-3", "[OrientationAnalysis] WriteTestDataStructure(dataStructure, fmt::format("{}/write_pole_figure-3.dream3d", unit_test::k_BinaryTestOutputDir)); #endif - CompareArrays(dataStructure, exemplarImageData, calculatedImageData); + CompareComponentsOfArrays(dataStructure, exemplarImageData, calculatedImageData, 0); + CompareComponentsOfArrays(dataStructure, exemplarImageData, calculatedImageData, 1); + CompareComponentsOfArrays(dataStructure, exemplarImageData, calculatedImageData, 2); }