diff --git a/src/Plugins/ITKImageProcessing/CMakeLists.txt b/src/Plugins/ITKImageProcessing/CMakeLists.txt index ee7367bd08..239bda2a10 100644 --- a/src/Plugins/ITKImageProcessing/CMakeLists.txt +++ b/src/Plugins/ITKImageProcessing/CMakeLists.txt @@ -167,9 +167,9 @@ if(NOT ITKIMAGEPROCESSING_LEAN_AND_MEAN) # ITKBinaryClosingByReconstructionImage # ITKBinomialBlurImage # ITKLaplacianSharpeningImage - # ITKMaximumProjectionImage - # ITKMedianProjectionImage - # ITKMinimumProjectionImage + ITKMaximumProjectionImageFilter + ITKMedianProjectionImageFilter + ITKMinimumProjectionImageFilter # ITKMultiScaleHessianBasedObjectnessImage # ITKSaltAndPepperNoiseImage # ITKShiftScaleImage @@ -182,7 +182,7 @@ if(NOT ITKIMAGEPROCESSING_LEAN_AND_MEAN) # ----------------------------------------------------------------------------- # ITKBoundedReciprocalImage # ITKNormalizeToConstantImage - # ITKMeanProjectionImage + ITKMeanProjectionImageFilter # ITKStandardDeviationProjectionImage # ITKSumProjectionImage # ITKMorphologicalWatershedFromMarkersImage @@ -265,6 +265,7 @@ set(${PLUGIN_NAME}_Common_Srcs ${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/Common/ITKArrayHelper.cpp ${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/Common/ITKProgressObserver.hpp ${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/Common/ITKDream3DFilterInterruption.hpp + ${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/Common/ProjectionUtils.hpp ${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/Common/ReadImageUtils.hpp ${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/Common/ReadImageUtils.cpp ) diff --git a/src/Plugins/ITKImageProcessing/docs/ITKMaximumProjectionImageFilter.md b/src/Plugins/ITKImageProcessing/docs/ITKMaximumProjectionImageFilter.md new file mode 100644 index 0000000000..e6dcd8eff6 --- /dev/null +++ b/src/Plugins/ITKImageProcessing/docs/ITKMaximumProjectionImageFilter.md @@ -0,0 +1,43 @@ +# ITK Maximum Projection Image Filter (ITKMaximumProjectionImage) + +Maximum projection. + +## Group (Subgroup) + +ITKImageStatistics (ImageStatistics) + +## Description + +This class was contributed to the Insight Journal by Gaetan Lehmann. The original paper can be found at https://www.insight-journal.org/browse/publication/71 + +## Author + +- Gaetan Lehmann. Biologie du Developpement et de la Reproduction, INRA de Jouy-en-Josas, France. + +## See Also + +- [ProjectionImageFilter](https://itk.org/Doxygen/html/classitk_1_1ProjectionImageFilter.html) + +- [MedianProjectionImageFilter](https://itk.org/Doxygen/html/classitk_1_1MedianProjectionImageFilter.html) + +- [MinimumProjectionImageFilter](https://itk.org/Doxygen/html/classitk_1_1MinimumProjectionImageFilter.html) + +- [StandardDeviationProjectionImageFilter](https://itk.org/Doxygen/html/classitk_1_1StandardDeviationProjectionImageFilter.html) + +- [SumProjectionImageFilter](https://itk.org/Doxygen/html/classitk_1_1SumProjectionImageFilter.html) + +- [BinaryProjectionImageFilter](https://itk.org/Doxygen/html/classitk_1_1BinaryProjectionImageFilter.html) + +- [MeanProjectionImageFilter](https://itk.org/Doxygen/html/classitk_1_1MeanProjectionImageFilter.html) + +% Auto generated parameter table will be inserted here + +## 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/ITKImageProcessing/docs/ITKMeanProjectionImageFilter.md b/src/Plugins/ITKImageProcessing/docs/ITKMeanProjectionImageFilter.md index 625507044b..b079821f1c 100644 --- a/src/Plugins/ITKImageProcessing/docs/ITKMeanProjectionImageFilter.md +++ b/src/Plugins/ITKImageProcessing/docs/ITKMeanProjectionImageFilter.md @@ -38,6 +38,6 @@ This class was contributed to the Insight Journal by Gaetan Lehmann. The origina Please see the description file distributed with this plugin. -## DREAM3D Mailing Lists +## 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/ITKImageProcessing/docs/ITKMedianProjectionImageFilter.md b/src/Plugins/ITKImageProcessing/docs/ITKMedianProjectionImageFilter.md new file mode 100644 index 0000000000..9edf342eca --- /dev/null +++ b/src/Plugins/ITKImageProcessing/docs/ITKMedianProjectionImageFilter.md @@ -0,0 +1,43 @@ +# ITK Median Projection Image Filter (ITKMedianProjectionImage) + +Median projection. + +## Group (Subgroup) + +ITKImageStatistics (ImageStatistics) + +## Description + +This class was contributed to the Insight Journal by Gaetan Lehmann. The original paper can be found at https://www.insight-journal.org/browse/publication/71 + +## Author + +- Gaetan Lehmann. Biologie du Developpement et de la Reproduction, INRA de Jouy-en-Josas, France. + +## See Also + +- [ProjectionImageFilter](https://itk.org/Doxygen/html/classitk_1_1ProjectionImageFilter.html) + +- [MeanProjectionImageFilter](https://itk.org/Doxygen/html/classitk_1_1MeanProjectionImageFilter.html) + +- [MinimumProjectionImageFilter](https://itk.org/Doxygen/html/classitk_1_1MinimumProjectionImageFilter.html) + +- [StandardDeviationProjectionImageFilter](https://itk.org/Doxygen/html/classitk_1_1StandardDeviationProjectionImageFilter.html) + +- [SumProjectionImageFilter](https://itk.org/Doxygen/html/classitk_1_1SumProjectionImageFilter.html) + +- [BinaryProjectionImageFilter](https://itk.org/Doxygen/html/classitk_1_1BinaryProjectionImageFilter.html) + +- [MaximumProjectionImageFilter](https://itk.org/Doxygen/html/classitk_1_1MaximumProjectionImageFilter.html) + +% Auto generated parameter table will be inserted here + +## 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/ITKImageProcessing/docs/ITKMinimumProjectionImageFilter.md b/src/Plugins/ITKImageProcessing/docs/ITKMinimumProjectionImageFilter.md new file mode 100644 index 0000000000..b4ac3d85e8 --- /dev/null +++ b/src/Plugins/ITKImageProcessing/docs/ITKMinimumProjectionImageFilter.md @@ -0,0 +1,43 @@ +# ITK Minimum Projection Image Filter (ITKMinimumProjectionImage) + +Minimum projection. + +## Group (Subgroup) + +ITKImageStatistics (ImageStatistics) + +## Description + +This class was contributed to the Insight Journal by Gaetan Lehmann. The original paper can be found at https://www.insight-journal.org/browse/publication/71 + +## Author + +- Gaetan Lehmann. Biologie du Developpement et de la Reproduction, INRA de Jouy-en-Josas, France. + +## See Also + +- [ProjectionImageFilter](https://itk.org/Doxygen/html/classitk_1_1ProjectionImageFilter.html) + +- [MedianProjectionImageFilter](https://itk.org/Doxygen/html/classitk_1_1MedianProjectionImageFilter.html) + +- [MeanProjectionImageFilter](https://itk.org/Doxygen/html/classitk_1_1MeanProjectionImageFilter.html) + +- [StandardDeviationProjectionImageFilter](https://itk.org/Doxygen/html/classitk_1_1StandardDeviationProjectionImageFilter.html) + +- [SumProjectionImageFilter](https://itk.org/Doxygen/html/classitk_1_1SumProjectionImageFilter.html) + +- [BinaryProjectionImageFilter](https://itk.org/Doxygen/html/classitk_1_1BinaryProjectionImageFilter.html) + +- [MaximumProjectionImageFilter](https://itk.org/Doxygen/html/classitk_1_1MaximumProjectionImageFilter.html) + +% Auto generated parameter table will be inserted here + +## 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/ITKImageProcessing/src/ITKImageProcessing/Common/ProjectionUtils.hpp b/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Common/ProjectionUtils.hpp new file mode 100644 index 0000000000..8995b1771a --- /dev/null +++ b/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Common/ProjectionUtils.hpp @@ -0,0 +1,211 @@ +#pragma once + +#include "simplnx/Common/Result.hpp" +#include "simplnx/Common/Types.hpp" +#include "simplnx/Filter/Actions/CreateImageGeometryAction.hpp" +#include "simplnx/Filter/IFilter.hpp" + +#include "ITKArrayHelper.hpp" + +using namespace nx::core; + +namespace ProjectionUtilities +{ +// uint8, int16, uint16, float32 +using ITKProjectionSupportedOutputTypes = ArrayTypeOptions; + +template +struct FixedOutputTypeHelper +{ + template + using FilterOutputType = T; +}; + +template +struct RunITKProjectionDataCheckFunctor +{ + template + auto operator()(const DataStructure& dataStructure, const DataPath& selectedInputArray, const DataPath& imageGeomPath, const DataPath& outputArrayPath) const + { + return ITK::DataCheck::template FilterOutputType>(dataStructure, selectedInputArray, imageGeomPath, outputArrayPath); + } +}; + +template +struct RunITKProjectionExecuteFunctor +{ + template + auto operator()(DataStructure& dataStructure, const DataPath& selectedInputArray, const DataPath& imageGeomPath, const DataPath& outputArrayPath, ITKFunctorType&& itkFunctor, + const std::atomic_bool& shouldCancel) const + { + return ITK::Execute::template FilterOutputType>(dataStructure, selectedInputArray, imageGeomPath, outputArrayPath, itkFunctor, + shouldCancel); + } +}; + +template +auto RunTemplateFunctor(FuncT&& func, FallbackFuncT&& fallback, DataType dataType, ArgsT&&... args) +{ + if constexpr(ArrayTypeOptions::UsingBoolean) + { + if(dataType == DataType::boolean) + { + return func.template operator()(std::forward(args)...); + } + } + if constexpr(ArrayTypeOptions::UsingInt8) + { + if(dataType == DataType::int8) + { + return func.template operator()(std::forward(args)...); + } + } + if constexpr(ArrayTypeOptions::UsingInt16) + { + if(dataType == DataType::int16) + { + return func.template operator()(std::forward(args)...); + } + } + if constexpr(ArrayTypeOptions::UsingInt32) + { + if(dataType == DataType::int32) + { + return func.template operator()(std::forward(args)...); + } + } + if constexpr(ArrayTypeOptions::UsingInt64) + { + if(dataType == DataType::int64) + { + return func.template operator()(std::forward(args)...); + } + } + if constexpr(ArrayTypeOptions::UsingUInt8) + { + if(dataType == DataType::uint8) + { + return func.template operator()(std::forward(args)...); + } + } + if constexpr(ArrayTypeOptions::UsingUInt16) + { + if(dataType == DataType::uint16) + { + return func.template operator()(std::forward(args)...); + } + } + if constexpr(ArrayTypeOptions::UsingUInt32) + { + if(dataType == DataType::uint32) + { + return func.template operator()(std::forward(args)...); + } + } + if constexpr(ArrayTypeOptions::UsingUInt64) + { + if(dataType == DataType::uint64) + { + return func.template operator()(std::forward(args)...); + } + } + if constexpr(ArrayTypeOptions::UsingFloat32) + { + if(dataType == DataType::float32) + { + return func.template operator()(std::forward(args)...); + } + } + if constexpr(ArrayTypeOptions::UsingFloat64) + { + if(dataType == DataType::float64) + { + return func.template operator()(std::forward(args)...); + } + } + return fallback(dataType); +} + +template +IFilter::PreflightResult RunITKProjectionDataCheck(const DataStructure& dataStructure, const DataPath& selectedInputArray, const DataPath& imageGeomPath, const std::string& outputGeomName, + bool performInPlace, const std::string& outputArrayName) +{ + DataPath outputArrayPath = selectedInputArray.replaceName(outputArrayName); + Result resultOutputActions; + // The input geometry must be preserved, so we will just copy the needed array into newly created output geometry + if(!performInPlace) + { + DataPath outputGeomPath({outputGeomName}); + + const auto& originalGeometry = dataStructure.getDataRefAs(imageGeomPath); + + // Make copy of input geometry + resultOutputActions.value().appendAction(std::make_unique( + outputGeomPath, originalGeometry.getDimensions().toContainer(), originalGeometry.getOrigin().toContainer(), + originalGeometry.getSpacing().toContainer(), originalGeometry.getCellDataPath().getTargetName())); + + outputArrayPath = outputGeomPath.createChildPath(originalGeometry.getCellDataPath().getTargetName()).createChildPath(outputArrayName); + } + + auto fallbackFunc = [](DataType dataType) { + return MakeErrorResult(-76590, fmt::format("Input {} type is not currently supported. Please reach out to devs if you have a use case.", DataTypeToString(dataType))); + }; + + DataType type = dataStructure.getDataRefAs(selectedInputArray).getDataType(); + Result helperOutputActions = RunTemplateFunctor(RunITKProjectionDataCheckFunctor{}, fallbackFunc, type, dataStructure, + selectedInputArray, imageGeomPath, outputArrayPath); + + if(helperOutputActions.invalid()) + { + return {std::move(helperOutputActions)}; + } + + // Consolidate actions + resultOutputActions.value().actions.insert(resultOutputActions.value().actions.end(), helperOutputActions.value().actions.begin(), helperOutputActions.value().actions.end()); + + return {std::move(resultOutputActions)}; +} + +template +Result<> RunITKProjectionExecute(DataStructure& dataStructure, const DataPath& selectedInputArray, const DataPath& imageGeomPath, const std::atomic_bool& shouldCancel, + const std::string& outputArrayName, bool performInPlace, ITKFunctorType&& itkFunctor, const std::string& outputImageGeomName) +{ + DataPath outputArrayPath = selectedInputArray.replaceName(outputArrayName); + + DataPath finalImageGeomPath = imageGeomPath; + + if(!performInPlace) + { + const auto& originalGeometry = dataStructure.getDataRefAs(imageGeomPath); + + finalImageGeomPath = DataPath({outputImageGeomName}); + outputArrayPath = finalImageGeomPath.createChildPath(originalGeometry.getCellDataPath().getTargetName()).createChildPath(outputArrayName); + } + + DataType type = dataStructure.getDataRefAs(selectedInputArray).getDataType(); + + auto fallbackFunc = [](DataType dataType) { + return MakeErrorResult(-76591, fmt::format("Input {} type is not currently supported. Please reach out to devs if you have a use case.", DataTypeToString(dataType))); + }; + + Result<> result = RunTemplateFunctor(RunITKProjectionExecuteFunctor{}, fallbackFunc, type, dataStructure, selectedInputArray, + finalImageGeomPath, outputArrayPath, itkFunctor, shouldCancel); + + if(result.invalid()) + { + return result; + } + + auto& imageGeom = dataStructure.getDataRefAs(finalImageGeomPath); + auto iArrayTupleShape = dataStructure.getDataAs(outputArrayPath)->getTupleShape(); + + // Update the Image Geometry with the new dimensions + imageGeom.setDimensions({iArrayTupleShape[2], iArrayTupleShape[1], iArrayTupleShape[0]}); + + // Update the AttributeMatrix with the new tuple shape. THIS WILL ALSO CHANGE ANY OTHER DATA ARRAY THAT IS ALSO + // STORED IN THAT ATTRIBUTE MATRIX + dataStructure.getDataAs(outputArrayPath.getParent())->resizeTuples(iArrayTupleShape); + + return {}; +} +} // namespace ProjectionUtilities \ No newline at end of file diff --git a/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKBinaryProjectionImageFilter.cpp b/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKBinaryProjectionImageFilter.cpp index 7f0507da9d..ed12355052 100644 --- a/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKBinaryProjectionImageFilter.cpp +++ b/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKBinaryProjectionImageFilter.cpp @@ -3,7 +3,9 @@ #include "ITKImageProcessing/Common/ITKArrayHelper.hpp" #include "ITKImageProcessing/Common/sitkCommon.hpp" +#include "simplnx/Filter/Actions/CreateImageGeometryAction.hpp" #include "simplnx/Parameters/ArraySelectionParameter.hpp" +#include "simplnx/Parameters/BoolParameter.hpp" #include "simplnx/Parameters/DataGroupSelectionParameter.hpp" #include "simplnx/Parameters/DataObjectNameParameter.hpp" #include "simplnx/Parameters/GeometrySelectionParameter.hpp" @@ -77,6 +79,7 @@ Parameters ITKBinaryProjectionImageFilter::parameters() const Parameters params; params.insertSeparator(Parameters::Separator{"Input Parameter(s)"}); params.insert(std::make_unique(k_ProjectionDimension_Key, "Projection Dimension", "The dimension index to project. 0=Slowest moving dimension.", 0u)); + params.insertLinkableParameter(std::make_unique(k_RemoveOriginalGeometry_Key, "Perform In-Place", "Performs the projection in-place for the given Image Geometry", true)); params.insert(std::make_unique( k_ForegroundValue_Key, "Foreground Value", "Set the value in the image to consider as 'foreground'. Defaults to maximum value of PixelType. Subclasses may alias this to DilateValue or ErodeValue.", 1.0)); @@ -91,9 +94,12 @@ Parameters ITKBinaryProjectionImageFilter::parameters() const params.insert(std::make_unique(k_InputImageDataPath_Key, "Input Cell Data", "The image data that will be processed by this filter.", DataPath{}, nx::core::ITK::GetScalarPixelAllowedTypes())); - params.insertSeparator(Parameters::Separator{"Output Cell Data"}); - params.insert(std::make_unique(k_OutputImageArrayName_Key, "Output Cell Data", - "The result of the processing will be stored in this Data Array inside the same group as the input data.", "Output Image Data")); + params.insertSeparator(Parameters::Separator{"Output Data"}); + params.insert(std::make_unique(k_OutputImageGeomName_Key, "Created Image Geometry", "The name of the projected geometry", "Projected Image")); + params.insert( + std::make_unique(k_OutputImageArrayName_Key, "Output Image Data Array", "The result of the processing will be stored in this Data Array.", "Output Image Data")); + + params.linkParameters(k_RemoveOriginalGeometry_Key, k_OutputImageGeomName_Key, false); return params; } @@ -101,7 +107,7 @@ Parameters ITKBinaryProjectionImageFilter::parameters() const //------------------------------------------------------------------------------ IFilter::VersionType ITKBinaryProjectionImageFilter::parametersVersion() const { - return 1; + return 2; } //------------------------------------------------------------------------------ @@ -120,9 +126,30 @@ IFilter::PreflightResult ITKBinaryProjectionImageFilter::preflightImpl(const Dat auto projectionDimension = filterArgs.value(k_ProjectionDimension_Key); auto foregroundValue = filterArgs.value(k_ForegroundValue_Key); auto backgroundValue = filterArgs.value(k_BackgroundValue_Key); - const DataPath outputArrayPath = selectedInputArray.replaceName(outputArrayName); + auto preformInPlace = filterArgs.value(k_RemoveOriginalGeometry_Key); + auto outputGeomName = filterArgs.value(k_OutputImageGeomName_Key); + DataPath outputArrayPath = selectedInputArray.replaceName(outputArrayName); + + Result resultOutputActions; + // The input geometry must be preserved, so we will just copy the needed array into newly created output geometry + if(!preformInPlace) + { + DataPath outputGeomPath({outputGeomName}); - Result resultOutputActions = ITK::DataCheck(dataStructure, selectedInputArray, imageGeomPath, outputArrayPath); + const auto& originalGeometry = dataStructure.getDataRefAs(imageGeomPath); + + // Make copy of input geometry + resultOutputActions.value().appendAction(std::make_unique( + outputGeomPath, originalGeometry.getDimensions().toContainer(), originalGeometry.getOrigin().toContainer(), + originalGeometry.getSpacing().toContainer(), originalGeometry.getCellDataPath().getTargetName())); + + outputArrayPath = outputGeomPath.createChildPath(originalGeometry.getCellDataPath().getTargetName()).createChildPath(outputArrayName); + } + + Result helperOutputActions = ITK::DataCheck(dataStructure, selectedInputArray, imageGeomPath, outputArrayPath); + + // Consolidate actions + resultOutputActions.value().actions.insert(resultOutputActions.value().actions.end(), helperOutputActions.value().actions.begin(), helperOutputActions.value().actions.end()); return {std::move(resultOutputActions)}; } @@ -134,7 +161,17 @@ Result<> ITKBinaryProjectionImageFilter::executeImpl(DataStructure& dataStructur auto imageGeomPath = filterArgs.value(k_InputImageGeomPath_Key); auto selectedInputArray = filterArgs.value(k_InputImageDataPath_Key); auto outputArrayName = filterArgs.value(k_OutputImageArrayName_Key); - const DataPath outputArrayPath = selectedInputArray.replaceName(outputArrayName); + DataPath outputArrayPath = selectedInputArray.replaceName(outputArrayName); + + auto preformInPlace = filterArgs.value(k_RemoveOriginalGeometry_Key); + + if(!preformInPlace) + { + const auto& originalGeometry = dataStructure.getDataRefAs(imageGeomPath); + + imageGeomPath = DataPath({filterArgs.value(k_OutputImageGeomName_Key)}); + outputArrayPath = imageGeomPath.createChildPath(originalGeometry.getCellDataPath().getTargetName()).createChildPath(outputArrayName); + } auto projectionDimension = filterArgs.value(k_ProjectionDimension_Key); auto foregroundValue = filterArgs.value(k_ForegroundValue_Key); @@ -142,26 +179,23 @@ Result<> ITKBinaryProjectionImageFilter::executeImpl(DataStructure& dataStructur const cxITKBinaryProjectionImageFilter::ITKBinaryProjectionImageFunctor itkFunctor = {projectionDimension, foregroundValue, backgroundValue}; - auto& imageGeom = dataStructure.getDataRefAs(imageGeomPath); - auto result = ITK::Execute(dataStructure, selectedInputArray, imageGeomPath, outputArrayPath, itkFunctor, shouldCancel); + if(result.invalid()) + { + return result; + } - IArray& iArrayRef = dataStructure.getDataRefAs(outputArrayPath); - auto iArrayTupleShape = iArrayRef.getTupleShape(); - std::cout << fmt::format("{}", fmt::join(iArrayRef.getTupleShape(), ",")) << std::endl; + auto& imageGeom = dataStructure.getDataRefAs(imageGeomPath); + auto iArrayTupleShape = dataStructure.getDataAs(outputArrayPath)->getTupleShape(); // Update the Image Geometry with the new dimensions imageGeom.setDimensions({iArrayTupleShape[2], iArrayTupleShape[1], iArrayTupleShape[0]}); // Update the AttributeMatrix with the new tuple shape. THIS WILL ALSO CHANGE ANY OTHER DATA ARRAY THAT IS ALSO // STORED IN THAT ATTRIBUTE MATRIX - auto amPathVector = outputArrayPath.getPathVector(); - amPathVector.pop_back(); - DataPath amPath(amPathVector); - AttributeMatrix& attributeMatrix = dataStructure.getDataRefAs(amPath); - attributeMatrix.resizeTuples(iArrayTupleShape); + dataStructure.getDataAs(outputArrayPath.getParent())->resizeTuples(iArrayTupleShape); - return result; + return {}; } namespace diff --git a/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKBinaryProjectionImageFilter.hpp b/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKBinaryProjectionImageFilter.hpp index a489dd4459..322036e202 100644 --- a/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKBinaryProjectionImageFilter.hpp +++ b/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKBinaryProjectionImageFilter.hpp @@ -55,10 +55,12 @@ class ITKIMAGEPROCESSING_EXPORT ITKBinaryProjectionImageFilter : public IFilter ITKBinaryProjectionImageFilter& operator=(ITKBinaryProjectionImageFilter&&) noexcept = delete; // Parameter Keys + static inline constexpr StringLiteral k_ProjectionDimension_Key = "projection_dimension"; + static inline constexpr StringLiteral k_RemoveOriginalGeometry_Key = "remove_original_geometry"; static inline constexpr StringLiteral k_InputImageGeomPath_Key = "input_image_geometry_path"; static inline constexpr StringLiteral k_InputImageDataPath_Key = "input_image_data_path"; + static inline constexpr StringLiteral k_OutputImageGeomName_Key = "output_image_geometry_name"; static inline constexpr StringLiteral k_OutputImageArrayName_Key = "output_array_name"; - static inline constexpr StringLiteral k_ProjectionDimension_Key = "projection_dimension"; static inline constexpr StringLiteral k_ForegroundValue_Key = "foreground_value"; static inline constexpr StringLiteral k_BackgroundValue_Key = "background_value"; diff --git a/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKMaximumProjectionImageFilter.cpp b/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKMaximumProjectionImageFilter.cpp new file mode 100644 index 0000000000..0ccce7029a --- /dev/null +++ b/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKMaximumProjectionImageFilter.cpp @@ -0,0 +1,137 @@ +#include "ITKMaximumProjectionImageFilter.hpp" + +#include "simplnx/Common/TypesUtility.hpp" +#include "simplnx/DataStructure/IDataArray.hpp" +#include "simplnx/Parameters/ArraySelectionParameter.hpp" +#include "simplnx/Parameters/BoolParameter.hpp" +#include "simplnx/Parameters/DataObjectNameParameter.hpp" +#include "simplnx/Parameters/GeometrySelectionParameter.hpp" +#include "simplnx/Parameters/NumberParameter.hpp" +#include "simplnx/Parameters/StringParameter.hpp" + +#include "ITKImageProcessing/Common/ProjectionUtils.hpp" + +#include + +using namespace nx::core; + +namespace cxITKMaximumProjectionImageFilter +{ +using ArrayOptionsType = ITK::ScalarPixelIdTypeList; +// VectorPixelIDTypeList; + +struct ITKMaximumProjectionImageFilterFunctor +{ + uint32 projectionDimension = 0u; + + template + auto createFilter() const + { + using FilterType = itk::MaximumProjectionImageFilter; + auto filter = FilterType::New(); + filter->SetProjectionDimension(projectionDimension); + return filter; + } +}; +} // namespace cxITKMaximumProjectionImageFilter + +namespace nx::core +{ +//------------------------------------------------------------------------------ +std::string ITKMaximumProjectionImageFilter::name() const +{ + return FilterTraits::name; +} + +//------------------------------------------------------------------------------ +std::string ITKMaximumProjectionImageFilter::className() const +{ + return FilterTraits::className; +} + +//------------------------------------------------------------------------------ +Uuid ITKMaximumProjectionImageFilter::uuid() const +{ + return FilterTraits::uuid; +} + +//------------------------------------------------------------------------------ +std::string ITKMaximumProjectionImageFilter::humanName() const +{ + return "ITK Maximum Projection Image Filter"; +} + +//------------------------------------------------------------------------------ +std::vector ITKMaximumProjectionImageFilter::defaultTags() const +{ + return {className(), "ITKImageProcessing", "ITKMaximumProjectionImageFilter", "ITKImageStatistics", "ImageStatistics"}; +} + +//------------------------------------------------------------------------------ +Parameters ITKMaximumProjectionImageFilter::parameters() const +{ + Parameters params; + params.insertSeparator(Parameters::Separator{"Input Parameter(s)"}); + params.insert(std::make_unique(k_ProjectionDimension_Key, "Projection Dimension", "The dimension index to project. 0=Slowest moving dimension.", 0u)); + params.insertLinkableParameter(std::make_unique(k_RemoveOriginalGeometry_Key, "Perform In-Place", "Performs the projection in-place for the given Image Geometry", true)); + + params.insertSeparator(Parameters::Separator{"Input Cell Data"}); + params.insert(std::make_unique(k_InputImageGeomPath_Key, "Image Geometry", "Select the Image Geometry Group from the DataStructure.", DataPath({"Image Geometry"}), + GeometrySelectionParameter::AllowedTypes{IGeometry::Type::Image})); + params.insert(std::make_unique(k_InputImageDataPath_Key, "Input Cell Data", "The image data that will be processed by this filter.", DataPath{}, + nx::core::ITK::GetScalarPixelAllowedTypes())); + + params.insertSeparator(Parameters::Separator{"Output Data"}); + params.insert(std::make_unique(k_OutputImageGeomName_Key, "Created Image Geometry", "The name of the projected geometry", "Projected Image")); + params.insert( + std::make_unique(k_OutputImageArrayName_Key, "Output Image Data Array", "The result of the processing will be stored in this Data Array.", "Output Image Data")); + + params.linkParameters(k_RemoveOriginalGeometry_Key, k_OutputImageGeomName_Key, false); + + return params; +} + +//------------------------------------------------------------------------------ +IFilter::VersionType ITKMaximumProjectionImageFilter::parametersVersion() const +{ + return 1; +} + +//------------------------------------------------------------------------------ +IFilter::UniquePointer ITKMaximumProjectionImageFilter::clone() const +{ + return std::make_unique(); +} + +//------------------------------------------------------------------------------ +IFilter::PreflightResult ITKMaximumProjectionImageFilter::preflightImpl(const DataStructure& dataStructure, const Arguments& filterArgs, const MessageHandler& messageHandler, + const std::atomic_bool& shouldCancel) const +{ + auto imageGeomPath = filterArgs.value(k_InputImageGeomPath_Key); + auto selectedInputArray = filterArgs.value(k_InputImageDataPath_Key); + auto outputArrayName = filterArgs.value(k_OutputImageArrayName_Key); + auto projectionDimension = filterArgs.value(k_ProjectionDimension_Key); + auto performInPlace = filterArgs.value(k_RemoveOriginalGeometry_Key); + auto outputGeomName = filterArgs.value(k_OutputImageGeomName_Key); + + return ProjectionUtilities::RunITKProjectionDataCheck(dataStructure, selectedInputArray, imageGeomPath, outputGeomName, performInPlace, + outputArrayName); +} + +//------------------------------------------------------------------------------ +Result<> ITKMaximumProjectionImageFilter::executeImpl(DataStructure& dataStructure, const Arguments& filterArgs, const PipelineFilter* pipelineNode, const MessageHandler& messageHandler, + const std::atomic_bool& shouldCancel) const +{ + auto imageGeomPath = filterArgs.value(k_InputImageGeomPath_Key); + auto selectedInputArray = filterArgs.value(k_InputImageDataPath_Key); + auto outputArrayName = filterArgs.value(k_OutputImageArrayName_Key); + auto outputImageGeomName = filterArgs.value(k_OutputImageGeomName_Key); + auto performInPlace = filterArgs.value(k_RemoveOriginalGeometry_Key); + auto projectionDimension = filterArgs.value(k_ProjectionDimension_Key); + + const cxITKMaximumProjectionImageFilter::ITKMaximumProjectionImageFilterFunctor itkFunctor = {projectionDimension}; + + return ProjectionUtilities::RunITKProjectionExecute(dataStructure, selectedInputArray, imageGeomPath, shouldCancel, outputArrayName, + performInPlace, itkFunctor, outputImageGeomName); +} +} // namespace nx::core diff --git a/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKMaximumProjectionImageFilter.hpp b/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKMaximumProjectionImageFilter.hpp new file mode 100644 index 0000000000..162cb5e309 --- /dev/null +++ b/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKMaximumProjectionImageFilter.hpp @@ -0,0 +1,139 @@ +#pragma once + +#include "ITKImageProcessing/ITKImageProcessing_export.hpp" + +#include "simplnx/Filter/FilterTraits.hpp" +#include "simplnx/Filter/IFilter.hpp" + +namespace nx::core +{ +/** + * @class ITKMaximumProjectionImageFilter + * @brief Maximum projection. + * + * This class was contributed to the Insight Journal by Gaetan Lehmann. The original paper can be found at https://www.insight-journal.org/browse/publication/71 + * + * @author Gaetan Lehmann. Biologie du Developpement et de la Reproduction, INRA de Jouy-en-Josas, France. + * + * + * @see ProjectionImageFilter + * + * + * @see MedianProjectionImageFilter + * + * + * @see MinimumProjectionImageFilter + * + * + * @see StandardDeviationProjectionImageFilter + * + * + * @see SumProjectionImageFilter + * + * + * @see BinaryProjectionImageFilter + * + * + * @see MeanProjectionImageFilter + * + * ITK Module: ITKImageStatistics + * ITK Group: ImageStatistics + */ +class ITKIMAGEPROCESSING_EXPORT ITKMaximumProjectionImageFilter : public IFilter +{ +public: + ITKMaximumProjectionImageFilter() = default; + ~ITKMaximumProjectionImageFilter() noexcept override = default; + + ITKMaximumProjectionImageFilter(const ITKMaximumProjectionImageFilter&) = delete; + ITKMaximumProjectionImageFilter(ITKMaximumProjectionImageFilter&&) noexcept = delete; + + ITKMaximumProjectionImageFilter& operator=(const ITKMaximumProjectionImageFilter&) = delete; + ITKMaximumProjectionImageFilter& operator=(ITKMaximumProjectionImageFilter&&) noexcept = delete; + + // Parameter Keys + static inline constexpr StringLiteral k_ProjectionDimension_Key = "projection_dimension"; + static inline constexpr StringLiteral k_RemoveOriginalGeometry_Key = "remove_original_geometry"; + static inline constexpr StringLiteral k_InputImageGeomPath_Key = "input_image_geometry_path"; + static inline constexpr StringLiteral k_InputImageDataPath_Key = "input_image_data_path"; + static inline constexpr StringLiteral k_OutputImageGeomName_Key = "output_image_geometry_name"; + static inline constexpr StringLiteral k_OutputImageArrayName_Key = "output_array_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 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 + * @param shouldCancel Boolean that gets set if the filter should stop executing and return + * @return Returns a Result object with error or warning values if any of those occurred during execution of this function + */ + PreflightResult preflightImpl(const DataStructure& dataStructure, const Arguments& filterArgs, const MessageHandler& messageHandler, const std::atomic_bool& shouldCancel) const override; + + /** + * @brief Applies the filter's algorithm to the DataStructure with the given arguments. Returns any warnings/errors. + * On failure, there is no guarantee that the DataStructure is in a correct state. + * @param dataStructure The input DataStructure instance + * @param filterArgs These are the input values for each parameter that is required for the filter + * @param messageHandler The MessageHandler object + * @param shouldCancel Boolean that gets set if the filter should stop executing and return + * @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, ITKMaximumProjectionImageFilter, "6dfe9167-d77d-41c7-aebf-569d6190645d"); diff --git a/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKMeanProjectionImageFilter.cpp b/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKMeanProjectionImageFilter.cpp index c9fd14a17d..bcdb1e40fe 100644 --- a/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKMeanProjectionImageFilter.cpp +++ b/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKMeanProjectionImageFilter.cpp @@ -1,12 +1,14 @@ #include "ITKMeanProjectionImageFilter.hpp" #include "ITKImageProcessing/Common/ITKArrayHelper.hpp" -#include "ITKImageProcessing/Common/sitkCommon.hpp" +#include "simplnx/Filter/Actions/CreateImageGeometryAction.hpp" #include "simplnx/Parameters/ArraySelectionParameter.hpp" +#include "simplnx/Parameters/BoolParameter.hpp" #include "simplnx/Parameters/DataObjectNameParameter.hpp" #include "simplnx/Parameters/GeometrySelectionParameter.hpp" #include "simplnx/Parameters/NumberParameter.hpp" +#include "simplnx/Parameters/StringParameter.hpp" #include @@ -72,6 +74,7 @@ Parameters ITKMeanProjectionImageFilter::parameters() const Parameters params; params.insertSeparator(Parameters::Separator{"Input Parameter(s)"}); params.insert(std::make_unique(k_ProjectionDimension_Key, "Projection Dimension", "The dimension index to project. 0=Slowest moving dimension.", 0u)); + params.insertLinkableParameter(std::make_unique(k_RemoveOriginalGeometry_Key, "Perform In-Place", "Performs the projection in-place for the given Image Geometry", true)); params.insertSeparator(Parameters::Separator{"Input Cell Data"}); params.insert(std::make_unique(k_InputImageGeomPath_Key, "Image Geometry", "Select the Image Geometry Group from the DataStructure.", DataPath({"Image Geometry"}), @@ -79,10 +82,13 @@ Parameters ITKMeanProjectionImageFilter::parameters() const params.insert(std::make_unique(k_InputImageDataPath_Key, "Input Cell Data", "The image data that will be processed by this filter.", DataPath{}, nx::core::ITK::GetScalarPixelAllowedTypes())); - params.insertSeparator(Parameters::Separator{"Output Cell Data"}); + params.insertSeparator(Parameters::Separator{"Output Data"}); + params.insert(std::make_unique(k_OutputImageGeomName_Key, "Created Image Geometry", "The name of the projected geometry", "Projected Image")); params.insert( std::make_unique(k_OutputImageArrayName_Key, "Output Image Data Array", "The result of the processing will be stored in this Data Array.", "Output Image Data")); + params.linkParameters(k_RemoveOriginalGeometry_Key, k_OutputImageGeomName_Key, false); + return params; } @@ -106,11 +112,32 @@ IFilter::PreflightResult ITKMeanProjectionImageFilter::preflightImpl(const DataS auto selectedInputArray = filterArgs.value(k_InputImageDataPath_Key); auto outputArrayName = filterArgs.value(k_OutputImageArrayName_Key); auto projectionDimension = filterArgs.value(k_ProjectionDimension_Key); - const DataPath outputArrayPath = selectedInputArray.replaceName(outputArrayName); + auto preformInPlace = filterArgs.value(k_RemoveOriginalGeometry_Key); + auto outputGeomName = filterArgs.value(k_OutputImageGeomName_Key); + DataPath outputArrayPath = selectedInputArray.replaceName(outputArrayName); + + Result resultOutputActions; + // The input geometry must be preserved, so we will just copy the needed array into newly created output geometry + if(!preformInPlace) + { + DataPath outputGeomPath({outputGeomName}); + + const auto& originalGeometry = dataStructure.getDataRefAs(imageGeomPath); + + // Make copy of input geometry + resultOutputActions.value().appendAction(std::make_unique( + outputGeomPath, originalGeometry.getDimensions().toContainer(), originalGeometry.getOrigin().toContainer(), + originalGeometry.getSpacing().toContainer(), originalGeometry.getCellDataPath().getTargetName())); - Result resultOutputActions = + outputArrayPath = outputGeomPath.createChildPath(originalGeometry.getCellDataPath().getTargetName()).createChildPath(outputArrayName); + } + + Result helperOutputActions = ITK::DataCheck(dataStructure, selectedInputArray, imageGeomPath, outputArrayPath); + // Consolidate actions + resultOutputActions.value().actions.insert(resultOutputActions.value().actions.end(), helperOutputActions.value().actions.begin(), helperOutputActions.value().actions.end()); + return {std::move(resultOutputActions)}; } @@ -121,15 +148,39 @@ Result<> ITKMeanProjectionImageFilter::executeImpl(DataStructure& dataStructure, auto imageGeomPath = filterArgs.value(k_InputImageGeomPath_Key); auto selectedInputArray = filterArgs.value(k_InputImageDataPath_Key); auto outputArrayName = filterArgs.value(k_OutputImageArrayName_Key); - const DataPath outputArrayPath = selectedInputArray.replaceName(outputArrayName); + DataPath outputArrayPath = selectedInputArray.replaceName(outputArrayName); + + auto preformInPlace = filterArgs.value(k_RemoveOriginalGeometry_Key); + + if(!preformInPlace) + { + const auto& originalGeometry = dataStructure.getDataRefAs(imageGeomPath); + + imageGeomPath = DataPath({filterArgs.value(k_OutputImageGeomName_Key)}); + outputArrayPath = imageGeomPath.createChildPath(originalGeometry.getCellDataPath().getTargetName()).createChildPath(outputArrayName); + } auto projectionDimension = filterArgs.value(k_ProjectionDimension_Key); const cxITKMeanProjectionImageFilter::ITKMeanProjectionImageFilterFunctor itkFunctor = {projectionDimension}; + auto result = ITK::Execute(dataStructure, selectedInputArray, imageGeomPath, outputArrayPath, + itkFunctor, shouldCancel); + if(result.invalid()) + { + return result; + } + auto& imageGeom = dataStructure.getDataRefAs(imageGeomPath); + auto iArrayTupleShape = dataStructure.getDataAs(outputArrayPath)->getTupleShape(); + + // Update the Image Geometry with the new dimensions + imageGeom.setDimensions({iArrayTupleShape[2], iArrayTupleShape[1], iArrayTupleShape[0]}); + + // Update the AttributeMatrix with the new tuple shape. THIS WILL ALSO CHANGE ANY OTHER DATA ARRAY THAT IS ALSO + // STORED IN THAT ATTRIBUTE MATRIX + dataStructure.getDataAs(outputArrayPath.getParent())->resizeTuples(iArrayTupleShape); - return ITK::Execute(dataStructure, selectedInputArray, imageGeomPath, outputArrayPath, itkFunctor, - shouldCancel); + return {}; } } // namespace nx::core diff --git a/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKMeanProjectionImageFilter.hpp b/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKMeanProjectionImageFilter.hpp index 20970df02a..f72cf95c32 100644 --- a/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKMeanProjectionImageFilter.hpp +++ b/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKMeanProjectionImageFilter.hpp @@ -52,10 +52,12 @@ class ITKIMAGEPROCESSING_EXPORT ITKMeanProjectionImageFilter : public IFilter ITKMeanProjectionImageFilter& operator=(ITKMeanProjectionImageFilter&&) noexcept = delete; // Parameter Keys + static inline constexpr StringLiteral k_ProjectionDimension_Key = "projection_dimension"; + static inline constexpr StringLiteral k_RemoveOriginalGeometry_Key = "remove_original_geometry"; static inline constexpr StringLiteral k_InputImageGeomPath_Key = "input_image_geometry_path"; static inline constexpr StringLiteral k_InputImageDataPath_Key = "input_image_data_path"; + static inline constexpr StringLiteral k_OutputImageGeomName_Key = "output_image_geometry_name"; static inline constexpr StringLiteral k_OutputImageArrayName_Key = "output_array_name"; - static inline constexpr StringLiteral k_ProjectionDimension_Key = "projection_dimension"; /** * @brief Returns the name of the filter. @@ -134,4 +136,4 @@ class ITKIMAGEPROCESSING_EXPORT ITKMeanProjectionImageFilter : public IFilter }; } // namespace nx::core -SIMPLNX_DEF_FILTER_TRAITS(nx::core, ITKMeanProjectionImageFilter, "6418e0cb-1a6f-43c5-9de4-fdfbb7983809"); +SIMPLNX_DEF_FILTER_TRAITS(nx::core, ITKMeanProjectionImageFilter, "62ffddba-cc57-45fc-a93a-27914eea11ad"); diff --git a/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKMedianProjectionImageFilter.cpp b/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKMedianProjectionImageFilter.cpp new file mode 100644 index 0000000000..612c56aa92 --- /dev/null +++ b/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKMedianProjectionImageFilter.cpp @@ -0,0 +1,139 @@ +#include "ITKMedianProjectionImageFilter.hpp" + +#include "simplnx/Common/TypesUtility.hpp" +#include "simplnx/DataStructure/IDataArray.hpp" +#include "simplnx/Parameters/ArraySelectionParameter.hpp" +#include "simplnx/Parameters/BoolParameter.hpp" +#include "simplnx/Parameters/DataObjectNameParameter.hpp" +#include "simplnx/Parameters/GeometrySelectionParameter.hpp" +#include "simplnx/Parameters/NumberParameter.hpp" +#include "simplnx/Parameters/StringParameter.hpp" + +#include "ITKImageProcessing/Common/ProjectionUtils.hpp" + +#include + +using namespace nx::core; + +namespace cxITKMedianProjectionImageFilter +{ +using ArrayOptionsType = ITK::ScalarPixelIdTypeList; + +// Uncommenting below line enables RGB/ARGB images that are currently unsupported +// using ArrayOptionsType = ITK::ArrayOptions, ITK::ArrayUseAllTypes>; +// VectorPixelIDTypeList; + +struct ITKMedianProjectionImageFilterFunctor +{ + uint32 projectionDimension = 0u; + + template + auto createFilter() const + { + using FilterType = itk::MedianProjectionImageFilter; + auto filter = FilterType::New(); + filter->SetProjectionDimension(projectionDimension); + return filter; + } +}; +} // namespace cxITKMedianProjectionImageFilter + +namespace nx::core +{ +//------------------------------------------------------------------------------ +std::string ITKMedianProjectionImageFilter::name() const +{ + return FilterTraits::name; +} + +//------------------------------------------------------------------------------ +std::string ITKMedianProjectionImageFilter::className() const +{ + return FilterTraits::className; +} + +//------------------------------------------------------------------------------ +Uuid ITKMedianProjectionImageFilter::uuid() const +{ + return FilterTraits::uuid; +} + +//------------------------------------------------------------------------------ +std::string ITKMedianProjectionImageFilter::humanName() const +{ + return "ITK Median Projection Image Filter"; +} + +//------------------------------------------------------------------------------ +std::vector ITKMedianProjectionImageFilter::defaultTags() const +{ + return {className(), "ITKImageProcessing", "ITKMedianProjectionImageFilter", "ITKImageStatistics", "ImageStatistics"}; +} + +//------------------------------------------------------------------------------ +Parameters ITKMedianProjectionImageFilter::parameters() const +{ + Parameters params; + params.insertSeparator(Parameters::Separator{"Input Parameter(s)"}); + params.insert(std::make_unique(k_ProjectionDimension_Key, "Projection Dimension", "The dimension index to project. 0=Slowest moving dimension.", 0u)); + params.insertLinkableParameter(std::make_unique(k_RemoveOriginalGeometry_Key, "Perform In-Place", "Performs the projection in-place for the given Image Geometry", true)); + + params.insertSeparator(Parameters::Separator{"Input Cell Data"}); + params.insert(std::make_unique(k_InputImageGeomPath_Key, "Image Geometry", "Select the Image Geometry Group from the DataStructure.", DataPath({"Image Geometry"}), + GeometrySelectionParameter::AllowedTypes{IGeometry::Type::Image})); + params.insert(std::make_unique(k_InputImageDataPath_Key, "Input Cell Data", "The image data that will be processed by this filter.", DataPath{}, + nx::core::ITK::GetScalarPixelAllowedTypes())); + + params.insertSeparator(Parameters::Separator{"Output Data"}); + params.insert(std::make_unique(k_OutputImageGeomName_Key, "Created Image Geometry", "The name of the projected geometry", "Projected Image")); + params.insert( + std::make_unique(k_OutputImageArrayName_Key, "Output Image Data Array", "The result of the processing will be stored in this Data Array.", "Output Image Data")); + + params.linkParameters(k_RemoveOriginalGeometry_Key, k_OutputImageGeomName_Key, false); + + return params; +} + +//------------------------------------------------------------------------------ +IFilter::VersionType ITKMedianProjectionImageFilter::parametersVersion() const +{ + return 1; +} + +//------------------------------------------------------------------------------ +IFilter::UniquePointer ITKMedianProjectionImageFilter::clone() const +{ + return std::make_unique(); +} + +//------------------------------------------------------------------------------ +IFilter::PreflightResult ITKMedianProjectionImageFilter::preflightImpl(const DataStructure& dataStructure, const Arguments& filterArgs, const MessageHandler& messageHandler, + const std::atomic_bool& shouldCancel) const +{ + auto imageGeomPath = filterArgs.value(k_InputImageGeomPath_Key); + auto selectedInputArray = filterArgs.value(k_InputImageDataPath_Key); + auto outputArrayName = filterArgs.value(k_OutputImageArrayName_Key); + auto projectionDimension = filterArgs.value(k_ProjectionDimension_Key); + auto performInPlace = filterArgs.value(k_RemoveOriginalGeometry_Key); + auto outputGeomName = filterArgs.value(k_OutputImageGeomName_Key); + + return ProjectionUtilities::RunITKProjectionDataCheck(dataStructure, selectedInputArray, imageGeomPath, outputGeomName, performInPlace, outputArrayName); +} + +//------------------------------------------------------------------------------ +Result<> ITKMedianProjectionImageFilter::executeImpl(DataStructure& dataStructure, const Arguments& filterArgs, const PipelineFilter* pipelineNode, const MessageHandler& messageHandler, + const std::atomic_bool& shouldCancel) const +{ + auto imageGeomPath = filterArgs.value(k_InputImageGeomPath_Key); + auto selectedInputArray = filterArgs.value(k_InputImageDataPath_Key); + auto outputArrayName = filterArgs.value(k_OutputImageArrayName_Key); + auto outputImageGeomName = filterArgs.value(k_OutputImageGeomName_Key); + auto performInPlace = filterArgs.value(k_RemoveOriginalGeometry_Key); + auto projectionDimension = filterArgs.value(k_ProjectionDimension_Key); + + const cxITKMedianProjectionImageFilter::ITKMedianProjectionImageFilterFunctor itkFunctor = {projectionDimension}; + + return ProjectionUtilities::RunITKProjectionExecute(dataStructure, selectedInputArray, imageGeomPath, shouldCancel, outputArrayName, performInPlace, itkFunctor, + outputImageGeomName); +} +} // namespace nx::core diff --git a/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKMedianProjectionImageFilter.hpp b/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKMedianProjectionImageFilter.hpp new file mode 100644 index 0000000000..b2d04342dc --- /dev/null +++ b/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKMedianProjectionImageFilter.hpp @@ -0,0 +1,139 @@ +#pragma once + +#include "ITKImageProcessing/ITKImageProcessing_export.hpp" + +#include "simplnx/Filter/FilterTraits.hpp" +#include "simplnx/Filter/IFilter.hpp" + +namespace nx::core +{ +/** + * @class ITKMedianProjectionImageFilter + * @brief Median projection. + * + * This class was contributed to the Insight Journal by Gaetan Lehmann. The original paper can be found at https://www.insight-journal.org/browse/publication/71 + * + * @author Gaetan Lehmann. Biologie du Developpement et de la Reproduction, INRA de Jouy-en-Josas, France. + * + * + * @see ProjectionImageFilter + * + * + * @see MeanProjectionImageFilter + * + * + * @see MinimumProjectionImageFilter + * + * + * @see StandardDeviationProjectionImageFilter + * + * + * @see SumProjectionImageFilter + * + * + * @see BinaryProjectionImageFilter + * + * + * @see MaximumProjectionImageFilter + * + * ITK Module: ITKImageStatistics + * ITK Group: ImageStatistics + */ +class ITKIMAGEPROCESSING_EXPORT ITKMedianProjectionImageFilter : public IFilter +{ +public: + ITKMedianProjectionImageFilter() = default; + ~ITKMedianProjectionImageFilter() noexcept override = default; + + ITKMedianProjectionImageFilter(const ITKMedianProjectionImageFilter&) = delete; + ITKMedianProjectionImageFilter(ITKMedianProjectionImageFilter&&) noexcept = delete; + + ITKMedianProjectionImageFilter& operator=(const ITKMedianProjectionImageFilter&) = delete; + ITKMedianProjectionImageFilter& operator=(ITKMedianProjectionImageFilter&&) noexcept = delete; + + // Parameter Keys + static inline constexpr StringLiteral k_ProjectionDimension_Key = "projection_dimension"; + static inline constexpr StringLiteral k_RemoveOriginalGeometry_Key = "remove_original_geometry"; + static inline constexpr StringLiteral k_InputImageGeomPath_Key = "input_image_geometry_path"; + static inline constexpr StringLiteral k_InputImageDataPath_Key = "input_image_data_path"; + static inline constexpr StringLiteral k_OutputImageGeomName_Key = "output_image_geometry_name"; + static inline constexpr StringLiteral k_OutputImageArrayName_Key = "output_array_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 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 + * @param shouldCancel Boolean that gets set if the filter should stop executing and return + * @return Returns a Result object with error or warning values if any of those occurred during execution of this function + */ + PreflightResult preflightImpl(const DataStructure& dataStructure, const Arguments& filterArgs, const MessageHandler& messageHandler, const std::atomic_bool& shouldCancel) const override; + + /** + * @brief Applies the filter's algorithm to the DataStructure with the given arguments. Returns any warnings/errors. + * On failure, there is no guarantee that the DataStructure is in a correct state. + * @param dataStructure The input DataStructure instance + * @param filterArgs These are the input values for each parameter that is required for the filter + * @param messageHandler The MessageHandler object + * @param shouldCancel Boolean that gets set if the filter should stop executing and return + * @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, ITKMedianProjectionImageFilter, "00e48f6b-8a00-414f-b3d9-49d48a3f9a00"); diff --git a/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKMinimumProjectionImageFilter.cpp b/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKMinimumProjectionImageFilter.cpp new file mode 100644 index 0000000000..8e2daa213f --- /dev/null +++ b/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKMinimumProjectionImageFilter.cpp @@ -0,0 +1,137 @@ +#include "ITKMinimumProjectionImageFilter.hpp" + +#include "simplnx/Common/TypesUtility.hpp" +#include "simplnx/DataStructure/IDataArray.hpp" +#include "simplnx/Parameters/ArraySelectionParameter.hpp" +#include "simplnx/Parameters/BoolParameter.hpp" +#include "simplnx/Parameters/DataObjectNameParameter.hpp" +#include "simplnx/Parameters/GeometrySelectionParameter.hpp" +#include "simplnx/Parameters/NumberParameter.hpp" +#include "simplnx/Parameters/StringParameter.hpp" + +#include "ITKImageProcessing/Common/ProjectionUtils.hpp" + +#include + +using namespace nx::core; + +namespace cxITKMinimumProjectionImageFilter +{ +using ArrayOptionsType = ITK::ScalarPixelIdTypeList; +// VectorPixelIDTypeList; + +struct ITKMinimumProjectionImageFilterFunctor +{ + uint32 projectionDimension = 0u; + + template + auto createFilter() const + { + using FilterType = itk::MinimumProjectionImageFilter; + auto filter = FilterType::New(); + filter->SetProjectionDimension(projectionDimension); + return filter; + } +}; +} // namespace cxITKMinimumProjectionImageFilter + +namespace nx::core +{ +//------------------------------------------------------------------------------ +std::string ITKMinimumProjectionImageFilter::name() const +{ + return FilterTraits::name; +} + +//------------------------------------------------------------------------------ +std::string ITKMinimumProjectionImageFilter::className() const +{ + return FilterTraits::className; +} + +//------------------------------------------------------------------------------ +Uuid ITKMinimumProjectionImageFilter::uuid() const +{ + return FilterTraits::uuid; +} + +//------------------------------------------------------------------------------ +std::string ITKMinimumProjectionImageFilter::humanName() const +{ + return "ITK Minimum Projection Image Filter"; +} + +//------------------------------------------------------------------------------ +std::vector ITKMinimumProjectionImageFilter::defaultTags() const +{ + return {className(), "ITKImageProcessing", "ITKMinimumProjectionImageFilter", "ITKImageStatistics", "ImageStatistics"}; +} + +//------------------------------------------------------------------------------ +Parameters ITKMinimumProjectionImageFilter::parameters() const +{ + Parameters params; + params.insertSeparator(Parameters::Separator{"Input Parameter(s)"}); + params.insert(std::make_unique(k_ProjectionDimension_Key, "Projection Dimension", "The dimension index to project. 0=Slowest moving dimension.", 0u)); + params.insertLinkableParameter(std::make_unique(k_RemoveOriginalGeometry_Key, "Perform In-Place", "Performs the projection in-place for the given Image Geometry", true)); + + params.insertSeparator(Parameters::Separator{"Input Cell Data"}); + params.insert(std::make_unique(k_InputImageGeomPath_Key, "Image Geometry", "Select the Image Geometry Group from the DataStructure.", DataPath({"Image Geometry"}), + GeometrySelectionParameter::AllowedTypes{IGeometry::Type::Image})); + params.insert(std::make_unique(k_InputImageDataPath_Key, "Input Cell Data", "The image data that will be processed by this filter.", DataPath{}, + nx::core::ITK::GetScalarPixelAllowedTypes())); + + params.insertSeparator(Parameters::Separator{"Output Data"}); + params.insert(std::make_unique(k_OutputImageGeomName_Key, "Created Image Geometry", "The name of the projected geometry", "Projected Image")); + params.insert( + std::make_unique(k_OutputImageArrayName_Key, "Output Image Data Array", "The result of the processing will be stored in this Data Array.", "Output Image Data")); + + params.linkParameters(k_RemoveOriginalGeometry_Key, k_OutputImageGeomName_Key, false); + + return params; +} + +//------------------------------------------------------------------------------ +IFilter::VersionType ITKMinimumProjectionImageFilter::parametersVersion() const +{ + return 1; +} + +//------------------------------------------------------------------------------ +IFilter::UniquePointer ITKMinimumProjectionImageFilter::clone() const +{ + return std::make_unique(); +} + +//------------------------------------------------------------------------------ +IFilter::PreflightResult ITKMinimumProjectionImageFilter::preflightImpl(const DataStructure& dataStructure, const Arguments& filterArgs, const MessageHandler& messageHandler, + const std::atomic_bool& shouldCancel) const +{ + auto imageGeomPath = filterArgs.value(k_InputImageGeomPath_Key); + auto selectedInputArray = filterArgs.value(k_InputImageDataPath_Key); + auto outputArrayName = filterArgs.value(k_OutputImageArrayName_Key); + auto projectionDimension = filterArgs.value(k_ProjectionDimension_Key); + auto performInPlace = filterArgs.value(k_RemoveOriginalGeometry_Key); + auto outputGeomName = filterArgs.value(k_OutputImageGeomName_Key); + + return ProjectionUtilities::RunITKProjectionDataCheck(dataStructure, selectedInputArray, imageGeomPath, outputGeomName, performInPlace, + outputArrayName); +} + +//------------------------------------------------------------------------------ +Result<> ITKMinimumProjectionImageFilter::executeImpl(DataStructure& dataStructure, const Arguments& filterArgs, const PipelineFilter* pipelineNode, const MessageHandler& messageHandler, + const std::atomic_bool& shouldCancel) const +{ + auto imageGeomPath = filterArgs.value(k_InputImageGeomPath_Key); + auto selectedInputArray = filterArgs.value(k_InputImageDataPath_Key); + auto outputArrayName = filterArgs.value(k_OutputImageArrayName_Key); + auto outputImageGeomName = filterArgs.value(k_OutputImageGeomName_Key); + auto performInPlace = filterArgs.value(k_RemoveOriginalGeometry_Key); + auto projectionDimension = filterArgs.value(k_ProjectionDimension_Key); + + const cxITKMinimumProjectionImageFilter::ITKMinimumProjectionImageFilterFunctor itkFunctor = {projectionDimension}; + + return ProjectionUtilities::RunITKProjectionExecute(dataStructure, selectedInputArray, imageGeomPath, shouldCancel, outputArrayName, + performInPlace, itkFunctor, outputImageGeomName); +} +} // namespace nx::core diff --git a/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKMinimumProjectionImageFilter.hpp b/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKMinimumProjectionImageFilter.hpp new file mode 100644 index 0000000000..2bbd9ff23b --- /dev/null +++ b/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKMinimumProjectionImageFilter.hpp @@ -0,0 +1,139 @@ +#pragma once + +#include "ITKImageProcessing/ITKImageProcessing_export.hpp" + +#include "simplnx/Filter/FilterTraits.hpp" +#include "simplnx/Filter/IFilter.hpp" + +namespace nx::core +{ +/** + * @class ITKMinimumProjectionImageFilter + * @brief Minimum projection. + * + * This class was contributed to the Insight Journal by Gaetan Lehmann. The original paper can be found at https://www.insight-journal.org/browse/publication/71 + * + * @author Gaetan Lehmann. Biologie du Developpement et de la Reproduction, INRA de Jouy-en-Josas, France. + * + * + * @see ProjectionImageFilter + * + * + * @see MedianProjectionImageFilter + * + * + * @see MeanProjectionImageFilter + * + * + * @see StandardDeviationProjectionImageFilter + * + * + * @see SumProjectionImageFilter + * + * + * @see BinaryProjectionImageFilter + * + * + * @see MaximumProjectionImageFilter + * + * ITK Module: ITKImageStatistics + * ITK Group: ImageStatistics + */ +class ITKIMAGEPROCESSING_EXPORT ITKMinimumProjectionImageFilter : public IFilter +{ +public: + ITKMinimumProjectionImageFilter() = default; + ~ITKMinimumProjectionImageFilter() noexcept override = default; + + ITKMinimumProjectionImageFilter(const ITKMinimumProjectionImageFilter&) = delete; + ITKMinimumProjectionImageFilter(ITKMinimumProjectionImageFilter&&) noexcept = delete; + + ITKMinimumProjectionImageFilter& operator=(const ITKMinimumProjectionImageFilter&) = delete; + ITKMinimumProjectionImageFilter& operator=(ITKMinimumProjectionImageFilter&&) noexcept = delete; + + // Parameter Keys + static inline constexpr StringLiteral k_ProjectionDimension_Key = "projection_dimension"; + static inline constexpr StringLiteral k_RemoveOriginalGeometry_Key = "remove_original_geometry"; + static inline constexpr StringLiteral k_InputImageGeomPath_Key = "input_image_geometry_path"; + static inline constexpr StringLiteral k_InputImageDataPath_Key = "input_image_data_path"; + static inline constexpr StringLiteral k_OutputImageGeomName_Key = "output_image_geometry_name"; + static inline constexpr StringLiteral k_OutputImageArrayName_Key = "output_array_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 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 + * @param shouldCancel Boolean that gets set if the filter should stop executing and return + * @return Returns a Result object with error or warning values if any of those occurred during execution of this function + */ + PreflightResult preflightImpl(const DataStructure& dataStructure, const Arguments& filterArgs, const MessageHandler& messageHandler, const std::atomic_bool& shouldCancel) const override; + + /** + * @brief Applies the filter's algorithm to the DataStructure with the given arguments. Returns any warnings/errors. + * On failure, there is no guarantee that the DataStructure is in a correct state. + * @param dataStructure The input DataStructure instance + * @param filterArgs These are the input values for each parameter that is required for the filter + * @param messageHandler The MessageHandler object + * @param shouldCancel Boolean that gets set if the filter should stop executing and return + * @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, ITKMinimumProjectionImageFilter, "86898336-8680-4c4e-b166-3f8de9e3d4f2"); diff --git a/src/Plugins/ITKImageProcessing/test/CMakeLists.txt b/src/Plugins/ITKImageProcessing/test/CMakeLists.txt index e1845ef933..60c57dcfe4 100644 --- a/src/Plugins/ITKImageProcessing/test/CMakeLists.txt +++ b/src/Plugins/ITKImageProcessing/test/CMakeLists.txt @@ -73,24 +73,27 @@ if(NOT ITKIMAGEPROCESSING_LEAN_AND_MEAN) # ITKLog10ImageTest.cpp # ITKLogImageTest.cpp ITKMaskImageTest.cpp - - # ITKMinMaxCurvatureFlowImageTest.cpp - # ITKNormalizeImageTest.cpp - # ITKNotImageTest.cpp - # ITKOtsuMultipleThresholdsImageTest.cpp - # ITKRegionalMaximaImageTest.cpp - # ITKRegionalMinimaImageTest.cpp - # ITKRelabelComponentImageTest.cpp - # ITKSigmoidImageTest.cpp - # ITKSignedDanielssonDistanceMapImageTest.cpp - # ITKSinImageTest.cpp - # ITKSqrtImageTest.cpp - # ITKSquareImageTest.cpp - # ITKTanImageTest.cpp - # ITKThresholdImageTest.cpp - # ITKThresholdMaximumConnectedComponentsImageTest.cpp - # ITKWhiteTopHatImageTest.cpp - # ITKZeroCrossingImageTest.cpp + ITKMaximumProjectionImageTest.cpp + ITKMeanProjectionImageTest.cpp + ITKMedianProjectionImageTest.cpp + ITKMinimumProjectionImageTest.cpp + ITKMinMaxCurvatureFlowImageTest.cpp + ITKNormalizeImageTest.cpp + ITKNotImageTest.cpp + ITKOtsuMultipleThresholdsImageTest.cpp + ITKRegionalMaximaImageTest.cpp + ITKRegionalMinimaImageTest.cpp + ITKRelabelComponentImageTest.cpp + ITKSigmoidImageTest.cpp + ITKSignedDanielssonDistanceMapImageTest.cpp + ITKSinImageTest.cpp + ITKSqrtImageTest.cpp + ITKSquareImageTest.cpp + ITKTanImageTest.cpp + ITKThresholdImageTest.cpp + ITKThresholdMaximumConnectedComponentsImageTest.cpp + ITKWhiteTopHatImageTest.cpp + ITKZeroCrossingImageTest.cpp ) endif() diff --git a/src/Plugins/ITKImageProcessing/test/ITKMaximumProjectionImageTest.cpp b/src/Plugins/ITKImageProcessing/test/ITKMaximumProjectionImageTest.cpp new file mode 100644 index 0000000000..ef274d5fd2 --- /dev/null +++ b/src/Plugins/ITKImageProcessing/test/ITKMaximumProjectionImageTest.cpp @@ -0,0 +1,192 @@ +#include + +#include "ITKImageProcessing/Common/sitkCommon.hpp" +#include "ITKImageProcessing/Filters/ITKMaximumProjectionImageFilter.hpp" +#include "ITKImageProcessing/ITKImageProcessing_test_dirs.hpp" +#include "ITKTestBase.hpp" + +#include "simplnx/Parameters/DataObjectNameParameter.hpp" +#include "simplnx/Parameters/NumberParameter.hpp" +#include "simplnx/UnitTest/UnitTestCommon.hpp" + +#include +namespace fs = std::filesystem; + +using namespace nx::core; +using namespace nx::core::Constants; +using namespace nx::core::UnitTest; + +TEST_CASE("ITKImageProcessing::ITKMaximumProjectionImageFilter: Default Test", "[ITKImageProcessing][ITKMaximumProjectionImage][defaults]") +{ + DataStructure dataStructure; + const ITKMaximumProjectionImageFilter filter; + + const DataPath inputGeometryPath({ITKTestBase::k_ImageGeometryPath}); + const DataPath cellDataPath = inputGeometryPath.createChildPath(ITKTestBase::k_ImageCellDataName); + const DataPath inputDataPath = cellDataPath.createChildPath(ITKTestBase::k_InputDataName); + const DataObjectNameParameter::ValueType outputArrayName = ITKTestBase::k_OutputDataPath; + + { // Start Image Comparison Scope + const fs::path inputFilePath = fs::path(unit_test::k_SourceDir.view()) / unit_test::k_DataDir.view() / "JSONFilters" / "Input/RA-Float.nrrd"; + Result<> imageReadResult = ITKTestBase::ReadImage(dataStructure, inputFilePath, inputGeometryPath, ITKTestBase::k_ImageCellDataName, ITKTestBase::k_InputDataName); + SIMPLNX_RESULT_REQUIRE_VALID(imageReadResult) + } // End Image Comparison Scope + + Arguments args; + args.insertOrAssign(ITKMaximumProjectionImageFilter::k_RemoveOriginalGeometry_Key, std::make_any(true)); + args.insertOrAssign(ITKMaximumProjectionImageFilter::k_InputImageGeomPath_Key, std::make_any(inputGeometryPath)); + args.insertOrAssign(ITKMaximumProjectionImageFilter::k_InputImageDataPath_Key, std::make_any(inputDataPath)); + args.insertOrAssign(ITKMaximumProjectionImageFilter::k_OutputImageArrayName_Key, std::make_any(outputArrayName)); + args.insertOrAssign(ITKMaximumProjectionImageFilter::k_ProjectionDimension_Key, std::make_any(0)); + + auto preflightResult = filter.preflight(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions) + + auto executeResult = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result) + + const std::string md5Hash = ITKTestBase::ComputeMd5Hash(dataStructure, cellDataPath.createChildPath(outputArrayName)); + REQUIRE(md5Hash == "fb78c55635b17fc9ff38ef0ef14f0948"); +} + +TEST_CASE("ITKImageProcessing::ITKMaximumProjectionImageFilter: New Geometry Default Test", "[ITKImageProcessing][ITKMaximumProjectionImage][defaults]") +{ + DataStructure dataStructure; + const ITKMaximumProjectionImageFilter filter; + + const DataPath inputGeometryPath({ITKTestBase::k_ImageGeometryPath}); + const DataPath cellDataPath = inputGeometryPath.createChildPath(ITKTestBase::k_ImageCellDataName); + const DataPath inputDataPath = cellDataPath.createChildPath(ITKTestBase::k_InputDataName); + const DataObjectNameParameter::ValueType outputArrayName = ITKTestBase::k_OutputDataPath; + const DataObjectNameParameter::ValueType outputImageName = "New Image Geometry"; + const DataPath outputDataPath = DataPath({outputImageName, ITKTestBase::k_ImageCellDataName, outputArrayName}); + + { // Start Image Comparison Scope + const fs::path inputFilePath = fs::path(unit_test::k_SourceDir.view()) / unit_test::k_DataDir.view() / "JSONFilters" / "Input/RA-Float.nrrd"; + Result<> imageReadResult = ITKTestBase::ReadImage(dataStructure, inputFilePath, inputGeometryPath, ITKTestBase::k_ImageCellDataName, ITKTestBase::k_InputDataName); + SIMPLNX_RESULT_REQUIRE_VALID(imageReadResult) + } // End Image Comparison Scope + + Arguments args; + args.insertOrAssign(ITKMaximumProjectionImageFilter::k_RemoveOriginalGeometry_Key, std::make_any(false)); + args.insertOrAssign(ITKMaximumProjectionImageFilter::k_InputImageGeomPath_Key, std::make_any(inputGeometryPath)); + args.insertOrAssign(ITKMaximumProjectionImageFilter::k_InputImageDataPath_Key, std::make_any(inputDataPath)); + args.insertOrAssign(ITKMaximumProjectionImageFilter::k_OutputImageArrayName_Key, std::make_any(outputArrayName)); + args.insertOrAssign(ITKMaximumProjectionImageFilter::k_OutputImageGeomName_Key, std::make_any(outputImageName)); + args.insertOrAssign(ITKMaximumProjectionImageFilter::k_ProjectionDimension_Key, std::make_any(0)); + + auto preflightResult = filter.preflight(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions) + + auto executeResult = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result) + + const std::string md5Hash = ITKTestBase::ComputeMd5Hash(dataStructure, outputDataPath); + REQUIRE(md5Hash == "fb78c55635b17fc9ff38ef0ef14f0948"); +} + +TEST_CASE("ITKImageProcessing::ITKMaximumProjectionImageFilter: Dimensional Test", "[ITKImageProcessing][ITKMaximumProjectionImage][defaults]") +{ + DataStructure dataStructure; + const ITKMaximumProjectionImageFilter filter; + + const DataPath inputGeometryPath({ITKTestBase::k_ImageGeometryPath}); + const DataPath cellDataPath = inputGeometryPath.createChildPath(ITKTestBase::k_ImageCellDataName); + const DataPath inputDataPath = cellDataPath.createChildPath(ITKTestBase::k_InputDataName); + const DataObjectNameParameter::ValueType outputArrayName = ITKTestBase::k_OutputDataPath; + + { // Start Image Comparison Scope + const fs::path inputFilePath = fs::path(unit_test::k_SourceDir.view()) / unit_test::k_DataDir.view() / "JSONFilters" / "Input/RA-Float.nrrd"; + Result<> imageReadResult = ITKTestBase::ReadImage(dataStructure, inputFilePath, inputGeometryPath, ITKTestBase::k_ImageCellDataName, ITKTestBase::k_InputDataName); + SIMPLNX_RESULT_REQUIRE_VALID(imageReadResult) + } // End Image Comparison Scope + + Arguments args; + args.insertOrAssign(ITKMaximumProjectionImageFilter::k_RemoveOriginalGeometry_Key, std::make_any(true)); + args.insertOrAssign(ITKMaximumProjectionImageFilter::k_InputImageGeomPath_Key, std::make_any(inputGeometryPath)); + args.insertOrAssign(ITKMaximumProjectionImageFilter::k_InputImageDataPath_Key, std::make_any(inputDataPath)); + args.insertOrAssign(ITKMaximumProjectionImageFilter::k_OutputImageArrayName_Key, std::make_any(outputArrayName)); + args.insertOrAssign(ITKMaximumProjectionImageFilter::k_ProjectionDimension_Key, std::make_any(2)); + + auto preflightResult = filter.preflight(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions) + + auto executeResult = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result) + + const std::string md5Hash = ITKTestBase::ComputeMd5Hash(dataStructure, cellDataPath.createChildPath(outputArrayName)); + REQUIRE(md5Hash == "f3f0d97c83c6b0d92df10c28e2481520"); +} + +TEST_CASE("ITKImageProcessing::ITKMaximumProjectionImageFilter: Image Short Test", "[ITKImageProcessing][ITKMaximumProjectionImage][defaults]") +{ + DataStructure dataStructure; + const ITKMaximumProjectionImageFilter filter; + + const DataPath inputGeometryPath({ITKTestBase::k_ImageGeometryPath}); + const DataPath cellDataPath = inputGeometryPath.createChildPath(ITKTestBase::k_ImageCellDataName); + const DataPath inputDataPath = cellDataPath.createChildPath(ITKTestBase::k_InputDataName); + const DataObjectNameParameter::ValueType outputArrayName = ITKTestBase::k_OutputDataPath; + + { // Start Image Comparison Scope + const fs::path inputFilePath = fs::path(unit_test::k_SourceDir.view()) / unit_test::k_DataDir.view() / "JSONFilters" / "Input/Ramp-Up-Short.nrrd"; + Result<> imageReadResult = ITKTestBase::ReadImage(dataStructure, inputFilePath, inputGeometryPath, ITKTestBase::k_ImageCellDataName, ITKTestBase::k_InputDataName); + SIMPLNX_RESULT_REQUIRE_VALID(imageReadResult) + } // End Image Comparison Scope + + Arguments args; + args.insertOrAssign(ITKMaximumProjectionImageFilter::k_RemoveOriginalGeometry_Key, std::make_any(true)); + args.insertOrAssign(ITKMaximumProjectionImageFilter::k_InputImageGeomPath_Key, std::make_any(inputGeometryPath)); + args.insertOrAssign(ITKMaximumProjectionImageFilter::k_InputImageDataPath_Key, std::make_any(inputDataPath)); + args.insertOrAssign(ITKMaximumProjectionImageFilter::k_OutputImageArrayName_Key, std::make_any(outputArrayName)); + args.insertOrAssign(ITKMaximumProjectionImageFilter::k_ProjectionDimension_Key, std::make_any(1)); + + auto preflightResult = filter.preflight(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions) + + auto executeResult = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result) + + const std::string md5Hash = ITKTestBase::ComputeMd5Hash(dataStructure, cellDataPath.createChildPath(outputArrayName)); + REQUIRE(md5Hash == "5390344262c91e83bc9208b0991a2fc9"); +} + +/** + * TODO: Review RGB/ARGB Image Functionality with Filter + * In the current implementation RGB/ARGB has been disabled due to the inability to validate test case. The below test + * case is an adaptation from SIMPL, however, the MD5 doesn't match. In old SIMPL, when you attempt to replicate pipeline + * an error states it doesn't allow RGB images, however the old test case is passing. For the time being the functionality + * has been disabled. + */ +// TEST_CASE("ITKImageProcessing::ITKMaximumProjectionImageFilter: Image RGB Test", "[ITKImageProcessing][ITKMaximumProjectionImage][defaults]") +//{ +// DataStructure dataStructure; +// const ITKMaximumProjectionImageFilter filter; +// +// const DataPath inputGeometryPath({ITKTestBase::k_ImageGeometryPath}); +// const DataPath cellDataPath = inputGeometryPath.createChildPath(ITKTestBase::k_ImageCellDataName); +// const DataPath inputDataPath = cellDataPath.createChildPath(ITKTestBase::k_InputDataName); +// const DataObjectNameParameter::ValueType outputArrayName = ITKTestBase::k_OutputDataPath; +// +// { // Start Image Comparison Scope +// const fs::path inputFilePath = fs::path(unit_test::k_SourceDir.view()) / unit_test::k_DataDir.view() / "JSONFilters" / "Input/VM1111Shrink-RGB.png"; +// Result<> imageReadResult = ITKTestBase::ReadImage(dataStructure, inputFilePath, inputGeometryPath, ITKTestBase::k_ImageCellDataName, ITKTestBase::k_InputDataName); +// SIMPLNX_RESULT_REQUIRE_VALID(imageReadResult) +// } // End Image Comparison Scope +// +// Arguments args; +// args.insertOrAssign(ITKMaximumProjectionImageFilter::k_RemoveOriginalGeometry_Key, std::make_any(true)); +// args.insertOrAssign(ITKMaximumProjectionImageFilter::k_InputImageGeomPath_Key, std::make_any(inputGeometryPath)); +// args.insertOrAssign(ITKMaximumProjectionImageFilter::k_InputImageDataPath_Key, std::make_any(inputDataPath)); +// args.insertOrAssign(ITKMaximumProjectionImageFilter::k_OutputImageArrayName_Key, std::make_any(outputArrayName)); +// args.insertOrAssign(ITKMaximumProjectionImageFilter::k_ProjectionDimension_Key, std::make_any(1)); +// +// auto preflightResult = filter.preflight(dataStructure, args); +// SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions) +// +// auto executeResult = filter.execute(dataStructure, args); +// SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result) +// +// const std::string md5Hash = ITKTestBase::ComputeMd5Hash(dataStructure, cellDataPath.createChildPath(outputArrayName)); +// REQUIRE(md5Hash == "92134e0dd38fccdf054ff23e67a72e75"); +// } diff --git a/src/Plugins/ITKImageProcessing/test/ITKMeanProjectionImageTest.cpp b/src/Plugins/ITKImageProcessing/test/ITKMeanProjectionImageTest.cpp index bacaaf739f..6d90962901 100644 --- a/src/Plugins/ITKImageProcessing/test/ITKMeanProjectionImageTest.cpp +++ b/src/Plugins/ITKImageProcessing/test/ITKMeanProjectionImageTest.cpp @@ -36,7 +36,7 @@ TEST_CASE("ITKImageProcessing::ITKMeanProjectionImageFilter(z_projection)", "[IT args.insertOrAssign(ITKMeanProjectionImageFilter::k_InputImageGeomPath_Key, std::make_any(inputGeometryPath)); args.insertOrAssign(ITKMeanProjectionImageFilter::k_InputImageDataPath_Key, std::make_any(inputDataPath)); args.insertOrAssign(ITKMeanProjectionImageFilter::k_OutputImageArrayName_Key, std::make_any(outputArrayName)); - args.insertOrAssign(ITKMeanProjectionImage::k_ProjectionDimension_Key, std::make_any(2)); + args.insertOrAssign(ITKMeanProjectionImageFilter::k_ProjectionDimension_Key, std::make_any(2)); auto preflightResult = filter.preflight(dataStructure, args); SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions) diff --git a/src/Plugins/ITKImageProcessing/test/ITKMedianProjectionImageTest.cpp b/src/Plugins/ITKImageProcessing/test/ITKMedianProjectionImageTest.cpp new file mode 100644 index 0000000000..76398a183b --- /dev/null +++ b/src/Plugins/ITKImageProcessing/test/ITKMedianProjectionImageTest.cpp @@ -0,0 +1,192 @@ +#include + +#include "ITKImageProcessing/Common/sitkCommon.hpp" +#include "ITKImageProcessing/Filters/ITKMedianProjectionImageFilter.hpp" +#include "ITKImageProcessing/ITKImageProcessing_test_dirs.hpp" +#include "ITKTestBase.hpp" + +#include "simplnx/Parameters/DataObjectNameParameter.hpp" +#include "simplnx/Parameters/NumberParameter.hpp" +#include "simplnx/UnitTest/UnitTestCommon.hpp" + +#include +namespace fs = std::filesystem; + +using namespace nx::core; +using namespace nx::core::Constants; +using namespace nx::core::UnitTest; + +TEST_CASE("ITKImageProcessing::ITKMedianProjectionImageFilter: Default Test", "[ITKImageProcessing][ITKMedianProjectionImage][defaults]") +{ + DataStructure dataStructure; + const ITKMedianProjectionImageFilter filter; + + const DataPath inputGeometryPath({ITKTestBase::k_ImageGeometryPath}); + const DataPath cellDataPath = inputGeometryPath.createChildPath(ITKTestBase::k_ImageCellDataName); + const DataPath inputDataPath = cellDataPath.createChildPath(ITKTestBase::k_InputDataName); + const DataObjectNameParameter::ValueType outputArrayName = ITKTestBase::k_OutputDataPath; + + { // Start Image Comparison Scope + const fs::path inputFilePath = fs::path(unit_test::k_SourceDir.view()) / unit_test::k_DataDir.view() / "JSONFilters" / "Input/RA-Float.nrrd"; + Result<> imageReadResult = ITKTestBase::ReadImage(dataStructure, inputFilePath, inputGeometryPath, ITKTestBase::k_ImageCellDataName, ITKTestBase::k_InputDataName); + SIMPLNX_RESULT_REQUIRE_VALID(imageReadResult) + } // End Image Comparison Scope + + Arguments args; + args.insertOrAssign(ITKMedianProjectionImageFilter::k_RemoveOriginalGeometry_Key, std::make_any(true)); + args.insertOrAssign(ITKMedianProjectionImageFilter::k_InputImageGeomPath_Key, std::make_any(inputGeometryPath)); + args.insertOrAssign(ITKMedianProjectionImageFilter::k_InputImageDataPath_Key, std::make_any(inputDataPath)); + args.insertOrAssign(ITKMedianProjectionImageFilter::k_OutputImageArrayName_Key, std::make_any(outputArrayName)); + args.insertOrAssign(ITKMedianProjectionImageFilter::k_ProjectionDimension_Key, std::make_any(0)); + + auto preflightResult = filter.preflight(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions) + + auto executeResult = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result) + + const std::string md5Hash = ITKTestBase::ComputeMd5Hash(dataStructure, cellDataPath.createChildPath(outputArrayName)); + REQUIRE(md5Hash == "86de48c070480cb9809e28715f6e70e1"); +} + +TEST_CASE("ITKImageProcessing::ITKMedianProjectionImageFilter: New Geometry Default Test", "[ITKImageProcessing][ITKMedianProjectionImage][defaults]") +{ + DataStructure dataStructure; + const ITKMedianProjectionImageFilter filter; + + const DataPath inputGeometryPath({ITKTestBase::k_ImageGeometryPath}); + const DataPath cellDataPath = inputGeometryPath.createChildPath(ITKTestBase::k_ImageCellDataName); + const DataPath inputDataPath = cellDataPath.createChildPath(ITKTestBase::k_InputDataName); + const DataObjectNameParameter::ValueType outputArrayName = ITKTestBase::k_OutputDataPath; + const DataObjectNameParameter::ValueType outputImageName = "New Image Geometry"; + const DataPath outputDataPath = DataPath({outputImageName, ITKTestBase::k_ImageCellDataName, outputArrayName}); + + { // Start Image Comparison Scope + const fs::path inputFilePath = fs::path(unit_test::k_SourceDir.view()) / unit_test::k_DataDir.view() / "JSONFilters" / "Input/RA-Float.nrrd"; + Result<> imageReadResult = ITKTestBase::ReadImage(dataStructure, inputFilePath, inputGeometryPath, ITKTestBase::k_ImageCellDataName, ITKTestBase::k_InputDataName); + SIMPLNX_RESULT_REQUIRE_VALID(imageReadResult) + } // End Image Comparison Scope + + Arguments args; + args.insertOrAssign(ITKMedianProjectionImageFilter::k_RemoveOriginalGeometry_Key, std::make_any(false)); + args.insertOrAssign(ITKMedianProjectionImageFilter::k_InputImageGeomPath_Key, std::make_any(inputGeometryPath)); + args.insertOrAssign(ITKMedianProjectionImageFilter::k_InputImageDataPath_Key, std::make_any(inputDataPath)); + args.insertOrAssign(ITKMedianProjectionImageFilter::k_OutputImageArrayName_Key, std::make_any(outputArrayName)); + args.insertOrAssign(ITKMedianProjectionImageFilter::k_OutputImageGeomName_Key, std::make_any(outputImageName)); + args.insertOrAssign(ITKMedianProjectionImageFilter::k_ProjectionDimension_Key, std::make_any(0)); + + auto preflightResult = filter.preflight(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions) + + auto executeResult = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result) + + const std::string md5Hash = ITKTestBase::ComputeMd5Hash(dataStructure, outputDataPath); + REQUIRE(md5Hash == "86de48c070480cb9809e28715f6e70e1"); +} + +TEST_CASE("ITKImageProcessing::ITKMedianProjectionImageFilter: Dimensional Test", "[ITKImageProcessing][ITKMedianProjectionImage][defaults]") +{ + DataStructure dataStructure; + const ITKMedianProjectionImageFilter filter; + + const DataPath inputGeometryPath({ITKTestBase::k_ImageGeometryPath}); + const DataPath cellDataPath = inputGeometryPath.createChildPath(ITKTestBase::k_ImageCellDataName); + const DataPath inputDataPath = cellDataPath.createChildPath(ITKTestBase::k_InputDataName); + const DataObjectNameParameter::ValueType outputArrayName = ITKTestBase::k_OutputDataPath; + + { // Start Image Comparison Scope + const fs::path inputFilePath = fs::path(unit_test::k_SourceDir.view()) / unit_test::k_DataDir.view() / "JSONFilters" / "Input/RA-Float.nrrd"; + Result<> imageReadResult = ITKTestBase::ReadImage(dataStructure, inputFilePath, inputGeometryPath, ITKTestBase::k_ImageCellDataName, ITKTestBase::k_InputDataName); + SIMPLNX_RESULT_REQUIRE_VALID(imageReadResult) + } // End Image Comparison Scope + + Arguments args; + args.insertOrAssign(ITKMedianProjectionImageFilter::k_RemoveOriginalGeometry_Key, std::make_any(true)); + args.insertOrAssign(ITKMedianProjectionImageFilter::k_InputImageGeomPath_Key, std::make_any(inputGeometryPath)); + args.insertOrAssign(ITKMedianProjectionImageFilter::k_InputImageDataPath_Key, std::make_any(inputDataPath)); + args.insertOrAssign(ITKMedianProjectionImageFilter::k_OutputImageArrayName_Key, std::make_any(outputArrayName)); + args.insertOrAssign(ITKMedianProjectionImageFilter::k_ProjectionDimension_Key, std::make_any(2)); + + auto preflightResult = filter.preflight(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions) + + auto executeResult = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result) + + const std::string md5Hash = ITKTestBase::ComputeMd5Hash(dataStructure, cellDataPath.createChildPath(outputArrayName)); + REQUIRE(md5Hash == "0990f0f6c63ea9d63b701ed7c2467de7"); +} + +TEST_CASE("ITKImageProcessing::ITKMedianProjectionImageFilter: Image Short Test", "[ITKImageProcessing][ITKMedianProjectionImage][defaults]") +{ + DataStructure dataStructure; + const ITKMedianProjectionImageFilter filter; + + const DataPath inputGeometryPath({ITKTestBase::k_ImageGeometryPath}); + const DataPath cellDataPath = inputGeometryPath.createChildPath(ITKTestBase::k_ImageCellDataName); + const DataPath inputDataPath = cellDataPath.createChildPath(ITKTestBase::k_InputDataName); + const DataObjectNameParameter::ValueType outputArrayName = ITKTestBase::k_OutputDataPath; + + { // Start Image Comparison Scope + const fs::path inputFilePath = fs::path(unit_test::k_SourceDir.view()) / unit_test::k_DataDir.view() / "JSONFilters" / "Input/Ramp-Up-Short.nrrd"; + Result<> imageReadResult = ITKTestBase::ReadImage(dataStructure, inputFilePath, inputGeometryPath, ITKTestBase::k_ImageCellDataName, ITKTestBase::k_InputDataName); + SIMPLNX_RESULT_REQUIRE_VALID(imageReadResult) + } // End Image Comparison Scope + + Arguments args; + args.insertOrAssign(ITKMedianProjectionImageFilter::k_RemoveOriginalGeometry_Key, std::make_any(true)); + args.insertOrAssign(ITKMedianProjectionImageFilter::k_InputImageGeomPath_Key, std::make_any(inputGeometryPath)); + args.insertOrAssign(ITKMedianProjectionImageFilter::k_InputImageDataPath_Key, std::make_any(inputDataPath)); + args.insertOrAssign(ITKMedianProjectionImageFilter::k_OutputImageArrayName_Key, std::make_any(outputArrayName)); + args.insertOrAssign(ITKMedianProjectionImageFilter::k_ProjectionDimension_Key, std::make_any(1)); + + auto preflightResult = filter.preflight(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions) + + auto executeResult = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result) + + const std::string md5Hash = ITKTestBase::ComputeMd5Hash(dataStructure, cellDataPath.createChildPath(outputArrayName)); + REQUIRE(md5Hash == "9fcc7164f3294811cbf2d875b0e494d1"); +} + +/** + * TODO: Review RGB/ARGB Image Functionality with Filter + * In the current implementation RGB/ARGB has been disabled due to the inability to validate test case. The below test + * case is an adaptation from SIMPL, however, the MD5 doesn't match. In old SIMPL, when you attempt to replicate pipeline + * an error states it doesn't allow RGB images, however the old test case is passing. For the time being the functionality + * has been disabled. + */ +// TEST_CASE("ITKImageProcessing::ITKMedianProjectionImageFilter: Image RGB Test", "[ITKImageProcessing][ITKMedianProjectionImage][defaults]") +//{ +// DataStructure dataStructure; +// const ITKMedianProjectionImageFilter filter; +// +// const DataPath inputGeometryPath({ITKTestBase::k_ImageGeometryPath}); +// const DataPath cellDataPath = inputGeometryPath.createChildPath(ITKTestBase::k_ImageCellDataName); +// const DataPath inputDataPath = cellDataPath.createChildPath(ITKTestBase::k_InputDataName); +// const DataObjectNameParameter::ValueType outputArrayName = ITKTestBase::k_OutputDataPath; +// +// { // Start Image Comparison Scope +// const fs::path inputFilePath = fs::path(unit_test::k_SourceDir.view()) / unit_test::k_DataDir.view() / "JSONFilters" / "Input/VM1111Shrink-RGB.png"; +// Result<> imageReadResult = ITKTestBase::ReadImage(dataStructure, inputFilePath, inputGeometryPath, ITKTestBase::k_ImageCellDataName, ITKTestBase::k_InputDataName); +// SIMPLNX_RESULT_REQUIRE_VALID(imageReadResult) +// } // End Image Comparison Scope +// +// Arguments args; +// args.insertOrAssign(ITKMedianProjectionImageFilter::k_RemoveOriginalGeometry_Key, std::make_any(true)); +// args.insertOrAssign(ITKMedianProjectionImageFilter::k_InputImageGeomPath_Key, std::make_any(inputGeometryPath)); +// args.insertOrAssign(ITKMedianProjectionImageFilter::k_InputImageDataPath_Key, std::make_any(inputDataPath)); +// args.insertOrAssign(ITKMedianProjectionImageFilter::k_OutputImageArrayName_Key, std::make_any(outputArrayName)); +// args.insertOrAssign(ITKMedianProjectionImageFilter::k_ProjectionDimension_Key, std::make_any(1)); +// +// auto preflightResult = filter.preflight(dataStructure, args); +// SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions) +// +// auto executeResult = filter.execute(dataStructure, args); +// SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result) +// +// const std::string md5Hash = ITKTestBase::ComputeMd5Hash(dataStructure, cellDataPath.createChildPath(outputArrayName)); +// REQUIRE(md5Hash == "b66bc7e92a21a33c46d9a334d2292845"); +// } diff --git a/src/Plugins/ITKImageProcessing/test/ITKMinimumProjectionImageTest.cpp b/src/Plugins/ITKImageProcessing/test/ITKMinimumProjectionImageTest.cpp new file mode 100644 index 0000000000..f16e3789d2 --- /dev/null +++ b/src/Plugins/ITKImageProcessing/test/ITKMinimumProjectionImageTest.cpp @@ -0,0 +1,192 @@ +#include + +#include "ITKImageProcessing/Common/sitkCommon.hpp" +#include "ITKImageProcessing/Filters/ITKMinimumProjectionImageFilter.hpp" +#include "ITKImageProcessing/ITKImageProcessing_test_dirs.hpp" +#include "ITKTestBase.hpp" + +#include "simplnx/Parameters/DataObjectNameParameter.hpp" +#include "simplnx/Parameters/NumberParameter.hpp" +#include "simplnx/UnitTest/UnitTestCommon.hpp" + +#include +namespace fs = std::filesystem; + +using namespace nx::core; +using namespace nx::core::Constants; +using namespace nx::core::UnitTest; + +TEST_CASE("ITKImageProcessing::ITKMinimumProjectionImageFilter: Default Test", "[ITKImageProcessing][ITKMinimumProjectionImage][defaults]") +{ + DataStructure dataStructure; + const ITKMinimumProjectionImageFilter filter; + + const DataPath inputGeometryPath({ITKTestBase::k_ImageGeometryPath}); + const DataPath cellDataPath = inputGeometryPath.createChildPath(ITKTestBase::k_ImageCellDataName); + const DataPath inputDataPath = cellDataPath.createChildPath(ITKTestBase::k_InputDataName); + const DataObjectNameParameter::ValueType outputArrayName = ITKTestBase::k_OutputDataPath; + + { // Start Image Comparison Scope + const fs::path inputFilePath = fs::path(unit_test::k_SourceDir.view()) / unit_test::k_DataDir.view() / "JSONFilters" / "Input/RA-Float.nrrd"; + Result<> imageReadResult = ITKTestBase::ReadImage(dataStructure, inputFilePath, inputGeometryPath, ITKTestBase::k_ImageCellDataName, ITKTestBase::k_InputDataName); + SIMPLNX_RESULT_REQUIRE_VALID(imageReadResult) + } // End Image Comparison Scope + + Arguments args; + args.insertOrAssign(ITKMinimumProjectionImageFilter::k_RemoveOriginalGeometry_Key, std::make_any(true)); + args.insertOrAssign(ITKMinimumProjectionImageFilter::k_InputImageGeomPath_Key, std::make_any(inputGeometryPath)); + args.insertOrAssign(ITKMinimumProjectionImageFilter::k_InputImageDataPath_Key, std::make_any(inputDataPath)); + args.insertOrAssign(ITKMinimumProjectionImageFilter::k_OutputImageArrayName_Key, std::make_any(outputArrayName)); + args.insertOrAssign(ITKMinimumProjectionImageFilter::k_ProjectionDimension_Key, std::make_any(0)); + + auto preflightResult = filter.preflight(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions) + + auto executeResult = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result) + + const std::string md5Hash = ITKTestBase::ComputeMd5Hash(dataStructure, cellDataPath.createChildPath(outputArrayName)); + REQUIRE(md5Hash == "5591e0307db733396e8cc8143e7f29f7"); +} + +TEST_CASE("ITKImageProcessing::ITKMinimumProjectionImageFilter: New Geometry Default Test", "[ITKImageProcessing][ITKMinimumProjectionImage][defaults]") +{ + DataStructure dataStructure; + const ITKMinimumProjectionImageFilter filter; + + const DataPath inputGeometryPath({ITKTestBase::k_ImageGeometryPath}); + const DataPath cellDataPath = inputGeometryPath.createChildPath(ITKTestBase::k_ImageCellDataName); + const DataPath inputDataPath = cellDataPath.createChildPath(ITKTestBase::k_InputDataName); + const DataObjectNameParameter::ValueType outputArrayName = ITKTestBase::k_OutputDataPath; + const DataObjectNameParameter::ValueType outputImageName = "New Image Geometry"; + const DataPath outputDataPath = DataPath({outputImageName, ITKTestBase::k_ImageCellDataName, outputArrayName}); + + { // Start Image Comparison Scope + const fs::path inputFilePath = fs::path(unit_test::k_SourceDir.view()) / unit_test::k_DataDir.view() / "JSONFilters" / "Input/RA-Float.nrrd"; + Result<> imageReadResult = ITKTestBase::ReadImage(dataStructure, inputFilePath, inputGeometryPath, ITKTestBase::k_ImageCellDataName, ITKTestBase::k_InputDataName); + SIMPLNX_RESULT_REQUIRE_VALID(imageReadResult) + } // End Image Comparison Scope + + Arguments args; + args.insertOrAssign(ITKMinimumProjectionImageFilter::k_RemoveOriginalGeometry_Key, std::make_any(false)); + args.insertOrAssign(ITKMinimumProjectionImageFilter::k_InputImageGeomPath_Key, std::make_any(inputGeometryPath)); + args.insertOrAssign(ITKMinimumProjectionImageFilter::k_InputImageDataPath_Key, std::make_any(inputDataPath)); + args.insertOrAssign(ITKMinimumProjectionImageFilter::k_OutputImageArrayName_Key, std::make_any(outputArrayName)); + args.insertOrAssign(ITKMinimumProjectionImageFilter::k_OutputImageGeomName_Key, std::make_any(outputImageName)); + args.insertOrAssign(ITKMinimumProjectionImageFilter::k_ProjectionDimension_Key, std::make_any(0)); + + auto preflightResult = filter.preflight(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions) + + auto executeResult = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result) + + const std::string md5Hash = ITKTestBase::ComputeMd5Hash(dataStructure, outputDataPath); + REQUIRE(md5Hash == "5591e0307db733396e8cc8143e7f29f7"); +} + +TEST_CASE("ITKImageProcessing::ITKMinimumProjectionImageFilter: Dimensional Test", "[ITKImageProcessing][ITKMinimumProjectionImage][defaults]") +{ + DataStructure dataStructure; + const ITKMinimumProjectionImageFilter filter; + + const DataPath inputGeometryPath({ITKTestBase::k_ImageGeometryPath}); + const DataPath cellDataPath = inputGeometryPath.createChildPath(ITKTestBase::k_ImageCellDataName); + const DataPath inputDataPath = cellDataPath.createChildPath(ITKTestBase::k_InputDataName); + const DataObjectNameParameter::ValueType outputArrayName = ITKTestBase::k_OutputDataPath; + + { // Start Image Comparison Scope + const fs::path inputFilePath = fs::path(unit_test::k_SourceDir.view()) / unit_test::k_DataDir.view() / "JSONFilters" / "Input/RA-Float.nrrd"; + Result<> imageReadResult = ITKTestBase::ReadImage(dataStructure, inputFilePath, inputGeometryPath, ITKTestBase::k_ImageCellDataName, ITKTestBase::k_InputDataName); + SIMPLNX_RESULT_REQUIRE_VALID(imageReadResult) + } // End Image Comparison Scope + + Arguments args; + args.insertOrAssign(ITKMinimumProjectionImageFilter::k_RemoveOriginalGeometry_Key, std::make_any(true)); + args.insertOrAssign(ITKMinimumProjectionImageFilter::k_InputImageGeomPath_Key, std::make_any(inputGeometryPath)); + args.insertOrAssign(ITKMinimumProjectionImageFilter::k_InputImageDataPath_Key, std::make_any(inputDataPath)); + args.insertOrAssign(ITKMinimumProjectionImageFilter::k_OutputImageArrayName_Key, std::make_any(outputArrayName)); + args.insertOrAssign(ITKMinimumProjectionImageFilter::k_ProjectionDimension_Key, std::make_any(2)); + + auto preflightResult = filter.preflight(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions) + + auto executeResult = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result) + + const std::string md5Hash = ITKTestBase::ComputeMd5Hash(dataStructure, cellDataPath.createChildPath(outputArrayName)); + REQUIRE(md5Hash == "6c16b87a823ca190294ac8b678ba4300"); +} + +TEST_CASE("ITKImageProcessing::ITKMinimumProjectionImageFilter: Image Short Test", "[ITKImageProcessing][ITKMinimumProjectionImage][defaults]") +{ + DataStructure dataStructure; + const ITKMinimumProjectionImageFilter filter; + + const DataPath inputGeometryPath({ITKTestBase::k_ImageGeometryPath}); + const DataPath cellDataPath = inputGeometryPath.createChildPath(ITKTestBase::k_ImageCellDataName); + const DataPath inputDataPath = cellDataPath.createChildPath(ITKTestBase::k_InputDataName); + const DataObjectNameParameter::ValueType outputArrayName = ITKTestBase::k_OutputDataPath; + + { // Start Image Comparison Scope + const fs::path inputFilePath = fs::path(unit_test::k_SourceDir.view()) / unit_test::k_DataDir.view() / "JSONFilters" / "Input/Ramp-Up-Short.nrrd"; + Result<> imageReadResult = ITKTestBase::ReadImage(dataStructure, inputFilePath, inputGeometryPath, ITKTestBase::k_ImageCellDataName, ITKTestBase::k_InputDataName); + SIMPLNX_RESULT_REQUIRE_VALID(imageReadResult) + } // End Image Comparison Scope + + Arguments args; + args.insertOrAssign(ITKMinimumProjectionImageFilter::k_RemoveOriginalGeometry_Key, std::make_any(true)); + args.insertOrAssign(ITKMinimumProjectionImageFilter::k_InputImageGeomPath_Key, std::make_any(inputGeometryPath)); + args.insertOrAssign(ITKMinimumProjectionImageFilter::k_InputImageDataPath_Key, std::make_any(inputDataPath)); + args.insertOrAssign(ITKMinimumProjectionImageFilter::k_OutputImageArrayName_Key, std::make_any(outputArrayName)); + args.insertOrAssign(ITKMinimumProjectionImageFilter::k_ProjectionDimension_Key, std::make_any(1)); + + auto preflightResult = filter.preflight(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions) + + auto executeResult = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result) + + const std::string md5Hash = ITKTestBase::ComputeMd5Hash(dataStructure, cellDataPath.createChildPath(outputArrayName)); + REQUIRE(md5Hash == "c4d83f61ffd5cc3a163155bb5d6a0698"); +} + +/** + * TODO: Review RGB/ARGB Image Functionality with Filter + * In the current implementation RGB/ARGB has been disabled due to the inability to validate test case. The below test + * case is an adaptation from SIMPL, however, the MD5 doesn't match. In old SIMPL, when you attempt to replicate pipeline + * an error states it doesn't allow RGB images, however the old test case is passing. For the time being the functionality + * has been disabled. + */ +// TEST_CASE("ITKImageProcessing::ITKMinimumProjectionImageFilter: Image RGB Test", "[ITKImageProcessing][ITKMinimumProjectionImage][defaults]") +//{ +// DataStructure dataStructure; +// const ITKMinimumProjectionImageFilter filter; +// +// const DataPath inputGeometryPath({ITKTestBase::k_ImageGeometryPath}); +// const DataPath cellDataPath = inputGeometryPath.createChildPath(ITKTestBase::k_ImageCellDataName); +// const DataPath inputDataPath = cellDataPath.createChildPath(ITKTestBase::k_InputDataName); +// const DataObjectNameParameter::ValueType outputArrayName = ITKTestBase::k_OutputDataPath; +// +// { // Start Image Comparison Scope +// const fs::path inputFilePath = fs::path(unit_test::k_SourceDir.view()) / unit_test::k_DataDir.view() / "JSONFilters" / "Input/VM1111Shrink-RGB.png"; +// Result<> imageReadResult = ITKTestBase::ReadImage(dataStructure, inputFilePath, inputGeometryPath, ITKTestBase::k_ImageCellDataName, ITKTestBase::k_InputDataName); +// SIMPLNX_RESULT_REQUIRE_VALID(imageReadResult) +// } // End Image Comparison Scope +// +// Arguments args; +// args.insertOrAssign(ITKMinimumProjectionImageFilter::k_RemoveOriginalGeometry_Key, std::make_any(true)); +// args.insertOrAssign(ITKMinimumProjectionImageFilter::k_InputImageGeomPath_Key, std::make_any(inputGeometryPath)); +// args.insertOrAssign(ITKMinimumProjectionImageFilter::k_InputImageDataPath_Key, std::make_any(inputDataPath)); +// args.insertOrAssign(ITKMinimumProjectionImageFilter::k_OutputImageArrayName_Key, std::make_any(outputArrayName)); +// args.insertOrAssign(ITKMinimumProjectionImageFilter::k_ProjectionDimension_Key, std::make_any(1)); +// +// auto preflightResult = filter.preflight(dataStructure, args); +// SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions) +// +// auto executeResult = filter.execute(dataStructure, args); +// SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result) +// +// const std::string md5Hash = ITKTestBase::ComputeMd5Hash(dataStructure, cellDataPath.createChildPath(outputArrayName)); +// REQUIRE(md5Hash == "344c2d7cf14b5e8b30b266b77a0548c2"); +// } diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/ComputeTriangleAreasFilter.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/ComputeTriangleAreasFilter.cpp index 8a67e51eee..e2c962c486 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/ComputeTriangleAreasFilter.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/ComputeTriangleAreasFilter.cpp @@ -8,17 +8,64 @@ #include "simplnx/Parameters/DataGroupSelectionParameter.hpp" #include "simplnx/Parameters/DataObjectNameParameter.hpp" #include "simplnx/Parameters/GeometrySelectionParameter.hpp" -#include "simplnx/Utilities/GeometryUtilities.hpp" #include "simplnx/Utilities/Math/MatrixMath.hpp" -#include "simplnx/Utilities/ParallelDataAlgorithm.hpp" + #include "simplnx/Utilities/SIMPLConversion.hpp" +#include "simplnx/Utilities/ParallelDataAlgorithm.hpp" + using namespace nx::core; namespace { constexpr nx::core::int32 k_MissingFeatureAttributeMatrix = -75769; +/** + * @brief The CalculateAreasImpl class implements a threaded algorithm that computes the area of each + * triangle for a set of triangles + */ +class CalculateAreasImpl +{ +public: + CalculateAreasImpl(const TriangleGeom* triangleGeom, Float64AbstractDataStore& areas, const std::atomic_bool& shouldCancel) + : m_TriangleGeom(triangleGeom) + , m_Areas(areas) + , m_ShouldCancel(shouldCancel) + { + } + virtual ~CalculateAreasImpl() = default; + + void convert(size_t start, size_t end) const + { + std::array cross = {0.0f, 0.0f, 0.0f}; + for(size_t triangleIndex = start; triangleIndex < end; triangleIndex++) + { + if(m_ShouldCancel) + { + break; + } + std::array vertCoords; + m_TriangleGeom->getFaceCoordinates(triangleIndex, vertCoords); + + auto vecA = (vertCoords[0] - vertCoords[1]).toArray(); + auto vecB = (vertCoords[0] - vertCoords[2]).toArray(); + + MatrixMath::CrossProduct(vecA.data(), vecB.data(), cross.data()); + + m_Areas[triangleIndex] = 0.5F * MatrixMath::Magnitude3x1(cross.data()); + } + } + + void operator()(const Range& range) const + { + convert(range.min(), range.max()); + } + +private: + const TriangleGeom* m_TriangleGeom = nullptr; + Float64AbstractDataStore& m_Areas; + const std::atomic_bool& m_ShouldCancel; +}; } // namespace namespace nx::core @@ -126,7 +173,12 @@ Result<> ComputeTriangleAreasFilter::executeImpl(DataStructure& dataStructure, c DataPath pCalculatedAreasDataPath = pTriangleGeometryDataPath.createChildPath(faceAttributeMatrix->getName()).createChildPath(pCalculatedAreasName); auto& faceAreas = dataStructure.getDataAs(pCalculatedAreasDataPath)->getDataStoreRef(); - return nx::core::GeometryUtilities::ComputeTriangleAreas(triangleGeom, faceAreas, shouldCancel); + // Parallel algorithm to find duplicate nodes + ParallelDataAlgorithm dataAlg; + dataAlg.setRange(0ULL, static_cast(triangleGeom->getNumberOfFaces())); + dataAlg.execute(CalculateAreasImpl(triangleGeom, faceAreas, shouldCancel)); + + return {}; } namespace diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/TriangleNormalFilter.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/TriangleNormalFilter.cpp index 7f2e5061e2..336bd8f20f 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/TriangleNormalFilter.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/TriangleNormalFilter.cpp @@ -7,7 +7,6 @@ #include "simplnx/Parameters/DataGroupSelectionParameter.hpp" #include "simplnx/Parameters/DataObjectNameParameter.hpp" #include "simplnx/Parameters/GeometrySelectionParameter.hpp" -#include "simplnx/Utilities/GeometryUtilities.hpp" #include "simplnx/Utilities/Math/MatrixMath.hpp" #include "simplnx/Utilities/ParallelDataAlgorithm.hpp" #include "simplnx/Utilities/SIMPLConversion.hpp" @@ -19,6 +18,56 @@ namespace constexpr nx::core::int32 k_MissingFeatureAttributeMatrix = -75969; +/** + * @brief The CalculateAreasImpl class implements a threaded algorithm that computes the normal of each + * triangle for a set of triangles + */ +class CalculateNormalsImpl +{ +public: + CalculateNormalsImpl(const TriangleGeom* triangleGeom, Float64AbstractDataStore& normals, const std::atomic_bool& shouldCancel) + : m_TriangleGeom(triangleGeom) + , m_Normals(normals) + , m_ShouldCancel(shouldCancel) + { + } + virtual ~CalculateNormalsImpl() = default; + + void generate(size_t start, size_t end) const + { + std::array normal = {0.0f, 0.0f, 0.0f}; + for(size_t triangleIndex = start; triangleIndex < end; triangleIndex++) + { + + if(m_ShouldCancel) + { + break; + } + std::array vertCoords; + m_TriangleGeom->getFaceCoordinates(triangleIndex, vertCoords); + + auto vecA = (vertCoords[1] - vertCoords[0]).toArray(); + auto vecB = (vertCoords[2] - vertCoords[0]).toArray(); + + MatrixMath::CrossProduct(vecA.data(), vecB.data(), normal.data()); + MatrixMath::Normalize3x1(normal.data()); + + m_Normals[triangleIndex * 3] = static_cast(normal[0]); + m_Normals[triangleIndex * 3 + 1] = static_cast(normal[1]); + m_Normals[triangleIndex * 3 + 2] = static_cast(normal[2]); + } + } + + void operator()(const Range& range) const + { + generate(range.min(), range.max()); + } + +private: + const TriangleGeom* m_TriangleGeom = nullptr; + Float64AbstractDataStore& m_Normals; + const std::atomic_bool& m_ShouldCancel; +}; } // namespace namespace nx::core @@ -123,7 +172,12 @@ Result<> TriangleNormalFilter::executeImpl(DataStructure& dataStructure, const A DataPath pNormalsArrayPath = pTriangleGeometryDataPath.createChildPath(faceAttributeMatrix->getName()).createChildPath(pNormalsName); auto& normals = dataStructure.getDataAs(pNormalsArrayPath)->getDataStoreRef(); - return nx::core::GeometryUtilities::ComputeTriangleNormals(triangleGeom, normals, shouldCancel); + // Parallel algorithm to find duplicate nodes + ParallelDataAlgorithm dataAlg; + dataAlg.setRange(0ULL, static_cast(triangleGeom->getNumberOfFaces())); + dataAlg.execute(CalculateNormalsImpl(triangleGeom, normals, shouldCancel)); + + return {}; } namespace diff --git a/src/simplnx/Utilities/GeometryUtilities.cpp b/src/simplnx/Utilities/GeometryUtilities.cpp index 4a31a2774c..a5c2797d76 100644 --- a/src/simplnx/Utilities/GeometryUtilities.cpp +++ b/src/simplnx/Utilities/GeometryUtilities.cpp @@ -2,7 +2,6 @@ #include "simplnx/Common/Array.hpp" #include "simplnx/Common/Result.hpp" -#include "simplnx/Utilities/Math/MatrixMath.hpp" #include