From fc8c653205be2a9916960901c1be653ab5cbc918 Mon Sep 17 00:00:00 2001 From: Joey Kleingers Date: Tue, 9 Dec 2025 13:36:53 -0500 Subject: [PATCH] FILT: ComputeEquivalentDiameterCirclesFilter Signed-off-by: Joey Kleingers --- CMakeLists.txt | 2 + .../ComputeEquivalentDiameterCirclesFilter.md | 28 ++++ .../ComputeEquivalentDiameterCircles.cpp | 84 +++++++++++ .../ComputeEquivalentDiameterCircles.hpp | 51 +++++++ ...ComputeEquivalentDiameterCirclesFilter.cpp | 134 ++++++++++++++++++ ...ComputeEquivalentDiameterCirclesFilter.hpp | 124 ++++++++++++++++ 6 files changed, 423 insertions(+) create mode 100644 docs/ComputeEquivalentDiameterCirclesFilter.md create mode 100644 src/SimplnxReview/Filters/Algorithms/ComputeEquivalentDiameterCircles.cpp create mode 100644 src/SimplnxReview/Filters/Algorithms/ComputeEquivalentDiameterCircles.hpp create mode 100644 src/SimplnxReview/Filters/ComputeEquivalentDiameterCirclesFilter.cpp create mode 100644 src/SimplnxReview/Filters/ComputeEquivalentDiameterCirclesFilter.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ca682c2..9379cce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,6 +30,7 @@ set(${PLUGIN_NAME}_SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}) # These are all the filters in the plugin. All filters should be kept in the # SimplnxReview/src/SimplnxReview/Filters/ directory. set(FilterList + ComputeEquivalentDiameterCirclesFilter ComputeLocalAverageCAxisMisalignmentsFilter ComputeMicroTextureRegionsFilter ComputeSaltykovSizesFilter @@ -46,6 +47,7 @@ set(ActionList # This should be integrated with the `create_simplnx_plugin` function call # ------------------------------------------------------------------------------ set(AlgorithmList + ComputeEquivalentDiameterCircles ComputeLocalAverageCAxisMisalignments ComputeMicroTextureRegions ComputeSaltykovSizes diff --git a/docs/ComputeEquivalentDiameterCirclesFilter.md b/docs/ComputeEquivalentDiameterCirclesFilter.md new file mode 100644 index 0000000..8abfe8f --- /dev/null +++ b/docs/ComputeEquivalentDiameterCirclesFilter.md @@ -0,0 +1,28 @@ +# Compute Equivalent Diameter Circles + +**THIS FILTER IS UNTESTED, UNVERIFIED AND UNVALIDATED. IT IS AN EXPERIMENTAL FILTER THAT IS UNDERGOING LONG TERM DEVELOPMENT +AND TESTING. USE AT YOUR OWN RISK** + +## Group (Subgroup) + +Visualization Helpers + +## Description + +This filter will generate an Edge Geometry that holds N number of circles where each circle is centered at a given centroid and the radius of the circle is based on the Equivalent Spherical Diameter(ESD) values that are also provided by the user. + +This filter was meant to generate 2D circles in the XY plane. This filter will not work well with 3D centroids or centroids that have differing Z Values. + +% Auto generated parameter table will be inserted here + +## References + +## 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/SimplnxReview/Filters/Algorithms/ComputeEquivalentDiameterCircles.cpp b/src/SimplnxReview/Filters/Algorithms/ComputeEquivalentDiameterCircles.cpp new file mode 100644 index 0000000..cb12331 --- /dev/null +++ b/src/SimplnxReview/Filters/Algorithms/ComputeEquivalentDiameterCircles.cpp @@ -0,0 +1,84 @@ +#include "ComputeEquivalentDiameterCircles.hpp" + +#include "simplnx/Common/Constants.hpp" +#include "simplnx/Common/Result.hpp" +#include "simplnx/Common/Types.hpp" +#include "simplnx/DataStructure/DataArray.hpp" +#include "simplnx/DataStructure/DataPath.hpp" +#include "simplnx/DataStructure/DataStructure.hpp" +#include "simplnx/DataStructure/Geometry/EdgeGeom.hpp" +#include "simplnx/Filter/IFilter.hpp" + +#include +#include + +using namespace nx::core; + +// ----------------------------------------------------------------------------- +ComputeEquivalentDiameterCircles::ComputeEquivalentDiameterCircles(DataStructure& dataStructure, const IFilter::MessageHandler& mesgHandler, const std::atomic_bool& shouldCancel, + ComputeEquivalentDiameterCirclesInputValues* inputValues) +: m_DataStructure(dataStructure) +, m_InputValues(inputValues) +, m_ShouldCancel(shouldCancel) +, m_MessageHandler(mesgHandler) +{ +} + +// ----------------------------------------------------------------------------- +const std::atomic_bool& ComputeEquivalentDiameterCircles::getCancel() +{ + return m_ShouldCancel; +} + +// ----------------------------------------------------------------------------- +Result<> ComputeEquivalentDiameterCircles::operator()() +{ + auto& centroidsArray = m_DataStructure.getDataRefAs(m_InputValues->CentroidsArrayPath); + auto& equivalentDiametersArray = m_DataStructure.getDataRefAs(m_InputValues->EquivalentDiametersArrayPath); + const DataPath featureIdsPath = m_InputValues->OutputEdgeGeometryPath.createChildPath(m_InputValues->EdgeAttributeMatrixName).createChildPath(m_InputValues->FeatureIdsArrayName); + auto& featureIdsArray = m_DataStructure.getDataRefAs(featureIdsPath); + auto& outputEdgeGeom = m_DataStructure.getDataRefAs(m_InputValues->OutputEdgeGeometryPath); + Float32Array& verticesArray = outputEdgeGeom.getVerticesRef(); + UInt64Array& edgesArray = outputEdgeGeom.getEdgesRef(); + + const usize circleResolution = m_InputValues->CircleResolution; + const usize verticesPerCircle = circleResolution + 1; + usize edgeIdx = 0; + + for(usize i = 1; i < centroidsArray.getNumberOfTuples(); ++i) + { + const float32 r = equivalentDiametersArray[i] / 2.0f; + const float32 cx = centroidsArray.getComponent(i, 0); + const float32 cy = centroidsArray.getComponent(i, 1); + + const usize circleVertexStart = (i - 1) * verticesPerCircle; + + for(usize v = 0; v < verticesPerCircle; ++v) + { + const usize vertexIdx = circleVertexStart + v; + + const float32 theta = 2.0f * nx::core::Constants::k_PiF * static_cast(v) / static_cast(circleResolution); + + const float32 x = cx + r * std::cos(theta); + const float32 y = cy + r * std::sin(theta); + + verticesArray.setComponent(vertexIdx, 0, x); + verticesArray.setComponent(vertexIdx, 1, y); + verticesArray.setComponent(vertexIdx, 2, static_cast(m_InputValues->ZPlane)); + } + + for(usize e = 0; e < circleResolution; ++e) + { + const usize v0 = circleVertexStart + e; + const usize v1 = circleVertexStart + e + 1; + + featureIdsArray.setValue(edgeIdx, static_cast(i)); + + edgesArray.setComponent(edgeIdx, 0, v0); + edgesArray.setComponent(edgeIdx, 1, v1); + edgeIdx++; + } + } + + return {}; +} diff --git a/src/SimplnxReview/Filters/Algorithms/ComputeEquivalentDiameterCircles.hpp b/src/SimplnxReview/Filters/Algorithms/ComputeEquivalentDiameterCircles.hpp new file mode 100644 index 0000000..3da783a --- /dev/null +++ b/src/SimplnxReview/Filters/Algorithms/ComputeEquivalentDiameterCircles.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include "SimplnxReview/SimplnxReview_export.hpp" + +#include "simplnx/DataStructure/DataPath.hpp" +#include "simplnx/DataStructure/DataStructure.hpp" +#include "simplnx/Filter/IFilter.hpp" + +namespace nx::core +{ + +struct SIMPLNXREVIEW_EXPORT ComputeEquivalentDiameterCirclesInputValues +{ + DataPath CentroidsArrayPath; + DataPath EquivalentDiametersArrayPath; + uint64 CircleResolution; + int64 ZPlane; + DataPath OutputEdgeGeometryPath; + std::string EdgeAttributeMatrixName; + std::string FeatureIdsArrayName; +}; + +/** + * @class ComputeEquivalentDiameterCircles + * @brief This filter determines the average C-axis location of each Feature. + */ + +class SIMPLNXREVIEW_EXPORT ComputeEquivalentDiameterCircles +{ +public: + ComputeEquivalentDiameterCircles(DataStructure& dataStructure, const IFilter::MessageHandler& mesgHandler, const std::atomic_bool& shouldCancel, + ComputeEquivalentDiameterCirclesInputValues* inputValues); + ~ComputeEquivalentDiameterCircles() noexcept = default; + + ComputeEquivalentDiameterCircles(const ComputeEquivalentDiameterCircles&) = delete; + ComputeEquivalentDiameterCircles(ComputeEquivalentDiameterCircles&&) noexcept = delete; + ComputeEquivalentDiameterCircles& operator=(const ComputeEquivalentDiameterCircles&) = delete; + ComputeEquivalentDiameterCircles& operator=(ComputeEquivalentDiameterCircles&&) noexcept = delete; + + Result<> operator()(); + + const std::atomic_bool& getCancel(); + +private: + DataStructure& m_DataStructure; + const ComputeEquivalentDiameterCirclesInputValues* m_InputValues = nullptr; + const std::atomic_bool& m_ShouldCancel; + const IFilter::MessageHandler& m_MessageHandler; +}; + +} // namespace nx::core diff --git a/src/SimplnxReview/Filters/ComputeEquivalentDiameterCirclesFilter.cpp b/src/SimplnxReview/Filters/ComputeEquivalentDiameterCirclesFilter.cpp new file mode 100644 index 0000000..1286813 --- /dev/null +++ b/src/SimplnxReview/Filters/ComputeEquivalentDiameterCirclesFilter.cpp @@ -0,0 +1,134 @@ +#include "ComputeEquivalentDiameterCirclesFilter.hpp" + +#include "SimplnxReview/Filters/Algorithms/ComputeEquivalentDiameterCircles.hpp" + +#include "simplnx/DataStructure/DataArray.hpp" +#include "simplnx/Filter/Actions/CreateArrayAction.hpp" +#include "simplnx/Filter/Actions/CreateGeometry1DAction.hpp" +#include "simplnx/Parameters/ArraySelectionParameter.hpp" +#include "simplnx/Parameters/DataGroupCreationParameter.hpp" +#include "simplnx/Parameters/DataObjectNameParameter.hpp" +#include "simplnx/Parameters/NumberParameter.hpp" + +using namespace nx::core; + +namespace nx::core +{ +//------------------------------------------------------------------------------ +std::string ComputeEquivalentDiameterCirclesFilter::name() const +{ + return FilterTraits::name.str(); +} + +//------------------------------------------------------------------------------ +std::string ComputeEquivalentDiameterCirclesFilter::className() const +{ + return FilterTraits::className; +} + +//------------------------------------------------------------------------------ +Uuid ComputeEquivalentDiameterCirclesFilter::uuid() const +{ + return FilterTraits::uuid; +} + +//------------------------------------------------------------------------------ +std::string ComputeEquivalentDiameterCirclesFilter::humanName() const +{ + return "Compute Equivalent Diameter Circles"; +} + +//------------------------------------------------------------------------------ +std::vector ComputeEquivalentDiameterCirclesFilter::defaultTags() const +{ + return {className()}; +} + +//------------------------------------------------------------------------------ +Parameters ComputeEquivalentDiameterCirclesFilter::parameters() const +{ + Parameters params; + // Create the parameter descriptors that are needed for this filter + params.insertSeparator(Parameters::Separator{"Input Parameter(s)"}); + params.insert(std::make_unique(k_CentroidsArrayPath_Key, "Feature Centroids", "X, Y, Z coordinates of Feature center of mass", DataPath{}, + ArraySelectionParameter::AllowedTypes{DataType::float32}, ArraySelectionParameter::AllowedComponentShapes{{3}})); + params.insert(std::make_unique(k_EquivalentDiametersArrayPath_Key, "Equivalent Diameters", "Input feature based Equivalent Diameters", DataPath{}, + ArraySelectionParameter::AllowedTypes{DataType::float32}, ArraySelectionParameter::AllowedComponentShapes{{1}})); + params.insert(std::make_unique>(k_CircleResolution_Key, "Circle Resolution", "The number of edges that each circle will have", 100)); + params.insert(std::make_unique>(k_ZPlane_Key, "Z Plane", "The Z plane that the circles will be calculated on.", 0)); + + params.insertSeparator(Parameters::Separator{"Output Edge Geometry"}); + params.insert(std::make_unique(k_OutputEdgeGeometryPath_Key, "Created Edge Geometry", "The name of the created Edge Geometry", DataPath({"Circles"}))); + params.insert(std::make_unique(k_EdgeAttributeMatrixName_Key, "Edge Attribute Matrix", "Attribute Matrix to store information about the created edges", "Edge Data")); + params.insert(std::make_unique(k_CreatedFeatureIdsArrayName_Key, "Edge Feature Ids", "Identifies the Feature Id to which each edge belongs", "Feature Ids")); + + return params; +} + +//------------------------------------------------------------------------------ +IFilter::UniquePointer ComputeEquivalentDiameterCirclesFilter::clone() const +{ + return std::make_unique(); +} + +//------------------------------------------------------------------------------ +IFilter::VersionType ComputeEquivalentDiameterCirclesFilter::parametersVersion() const +{ + return 1; +} + +//------------------------------------------------------------------------------ +IFilter::PreflightResult ComputeEquivalentDiameterCirclesFilter::preflightImpl(const DataStructure& dataStructure, const Arguments& filterArgs, const MessageHandler& messageHandler, + const std::atomic_bool& shouldCancel, const ExecutionContext& executionContext) const +{ + auto pCentroidsArrayPath = filterArgs.value(k_CentroidsArrayPath_Key); + auto pEquivalentDiametersArrayPath = filterArgs.value(k_EquivalentDiametersArrayPath_Key); + auto pCircleResolution = filterArgs.value(k_CircleResolution_Key); + auto pOutputEdgeGeometryPath = filterArgs.value(k_OutputEdgeGeometryPath_Key); + auto pEdgeAttributeMatrixName = filterArgs.value(k_EdgeAttributeMatrixName_Key); + auto pFeatureIdsArrayName = filterArgs.value(k_CreatedFeatureIdsArrayName_Key); + + Result resultOutputActions; + std::vector preflightUpdatedValues; + + auto& centroidsArray = dataStructure.getDataRefAs(pCentroidsArrayPath); + auto& equivalentDiametersArray = dataStructure.getDataRefAs(pEquivalentDiametersArrayPath); + usize numberOfCentroids = centroidsArray.getNumberOfTuples(); + if(numberOfCentroids != equivalentDiametersArray.getNumberOfTuples()) + { + return MakePreflightErrorResult(-13860, fmt::format("Centroids array has {} centroids, and the equivalent diameters array has {} diameters. These must be equal.", numberOfCentroids, + equivalentDiametersArray.getNumberOfTuples())); + } + + auto createGeometryAction = + std::make_unique(pOutputEdgeGeometryPath, pCircleResolution * (numberOfCentroids - 1), (pCircleResolution + 1) * (numberOfCentroids - 1), + INodeGeometry0D::k_VertexAttributeMatrixName, pEdgeAttributeMatrixName, EdgeGeom::k_SharedVertexListName, EdgeGeom::k_SharedEdgeListName); + resultOutputActions.value().appendAction(std::move(createGeometryAction)); + + DataPath path = pOutputEdgeGeometryPath.createChildPath(pEdgeAttributeMatrixName).createChildPath(pFeatureIdsArrayName); + auto createArray = std::make_unique(DataType::int32, std::vector{pCircleResolution * (numberOfCentroids - 1)}, std::vector{1}, path); + resultOutputActions.value().appendAction(std::move(createArray)); + + preflightUpdatedValues.push_back({"WARNING: This filter is experimental in nature and has not had any testing, validation or verification. Use at your own risk"}); + resultOutputActions.warnings().push_back({-65432, "WARNING: This filter is experimental in nature and has not had any testing, validation or verification. Use at your own risk"}); + + return {std::move(resultOutputActions), std::move(preflightUpdatedValues)}; +} + +//------------------------------------------------------------------------------ +Result<> ComputeEquivalentDiameterCirclesFilter::executeImpl(DataStructure& dataStructure, const Arguments& filterArgs, const PipelineFilter* pipelineNode, const MessageHandler& messageHandler, + const std::atomic_bool& shouldCancel, const ExecutionContext& executionContext) const +{ + ComputeEquivalentDiameterCirclesInputValues inputValues; + + inputValues.CentroidsArrayPath = filterArgs.value(k_CentroidsArrayPath_Key); + inputValues.EquivalentDiametersArrayPath = filterArgs.value(k_EquivalentDiametersArrayPath_Key); + inputValues.CircleResolution = filterArgs.value(k_CircleResolution_Key); + inputValues.ZPlane = filterArgs.value(k_ZPlane_Key); + inputValues.OutputEdgeGeometryPath = filterArgs.value(k_OutputEdgeGeometryPath_Key); + inputValues.EdgeAttributeMatrixName = filterArgs.value(k_EdgeAttributeMatrixName_Key); + inputValues.FeatureIdsArrayName = filterArgs.value(k_CreatedFeatureIdsArrayName_Key); + + return ComputeEquivalentDiameterCircles(dataStructure, messageHandler, shouldCancel, &inputValues)(); +} +} // namespace nx::core diff --git a/src/SimplnxReview/Filters/ComputeEquivalentDiameterCirclesFilter.hpp b/src/SimplnxReview/Filters/ComputeEquivalentDiameterCirclesFilter.hpp new file mode 100644 index 0000000..14d9880 --- /dev/null +++ b/src/SimplnxReview/Filters/ComputeEquivalentDiameterCirclesFilter.hpp @@ -0,0 +1,124 @@ +#pragma once + +#include "SimplnxReview/SimplnxReview_export.hpp" + +#include "simplnx/Filter/FilterTraits.hpp" +#include "simplnx/Filter/IFilter.hpp" + +namespace nx::core +{ +/** + * @class ComputeEquivalentDiameterCirclesFilter + * @brief This filter determines the average C-axis location of each Feature + */ +class SIMPLNXREVIEW_EXPORT ComputeEquivalentDiameterCirclesFilter : public IFilter +{ +public: + ComputeEquivalentDiameterCirclesFilter() = default; + ~ComputeEquivalentDiameterCirclesFilter() noexcept override = default; + + ComputeEquivalentDiameterCirclesFilter(const ComputeEquivalentDiameterCirclesFilter&) = delete; + ComputeEquivalentDiameterCirclesFilter(ComputeEquivalentDiameterCirclesFilter&&) noexcept = delete; + + ComputeEquivalentDiameterCirclesFilter& operator=(const ComputeEquivalentDiameterCirclesFilter&) = delete; + ComputeEquivalentDiameterCirclesFilter& operator=(ComputeEquivalentDiameterCirclesFilter&&) noexcept = delete; + + // Parameter Keys + static constexpr StringLiteral k_CentroidsArrayPath_Key = "centroids_array_path"; + static constexpr StringLiteral k_EquivalentDiametersArrayPath_Key = "equivalent_diameters_array_path"; + static constexpr StringLiteral k_CircleResolution_Key = "circle_resolution"; + static constexpr StringLiteral k_ZPlane_Key = "z_plane"; + static constexpr StringLiteral k_OutputEdgeGeometryPath_Key = "output_edge_geometry_path"; + static constexpr StringLiteral k_EdgeAttributeMatrixName_Key = "edge_attribute_matrix_name"; + static constexpr StringLiteral k_CreatedFeatureIdsArrayName_Key = "created_feature_ids_array_name"; + + /** + * @brief Reads SIMPL json and converts it simplnx Arguments. + * @param json + * @return Result + */ + static Result FromSIMPLJson(const nlohmann::json& json); + + /** + * @brief Returns the name of the filter. + * @return + */ + std::string name() const override; + + /** + * @brief Returns the C++ classname of this filter. + * @return + */ + std::string className() const override; + + /** + * @brief Returns the uuid of the filter. + * @return + */ + Uuid uuid() const override; + + /** + * @brief Returns the human-readable name of the filter. + * @return + */ + std::string humanName() const override; + + /** + * @brief Returns the default tags for this filter. + * @return + */ + std::vector defaultTags() const override; + + /** + * @brief Returns the parameters of the filter (i.e. its inputs) + * @return + */ + Parameters parameters() const override; + + /** + * @brief Returns parameters version integer. + * Initial version should always be 1. + * Should be incremented everytime the parameters change. + * @return VersionType + */ + VersionType parametersVersion() const override; + + /** + * @brief Returns a copy of the filter. + * @return + */ + UniquePointer clone() const override; + +protected: + /** + * @brief Takes in a DataStructure and checks that the filter can be run on it with the given arguments. + * Returns any warnings/errors. Also returns the changes that would be applied to the DataStructure. + * Some parts of the actions may not be completely filled out if all the required information is not available at preflight time. + * @param dataStructure The input DataStructure instance + * @param filterArgs These are the input values for each parameter that is required for the filter + * @param messageHandler The MessageHandler object + * @param shouldCancel Atomic boolean value that can be checked to cancel the filter + * @param executionContext The ExecutionContext that can be used to determine the correct absolute path from a relative path + * @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 ExecutionContext& executionContext) 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 pipelineNode The node in the pipeline that is being executed + * @param messageHandler The MessageHandler object + * @param shouldCancel Atomic boolean value that can be checked to cancel the filter + * @param executionContext The ExecutionContext that can be used to determine the correct absolute path from a relative path + * @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 ExecutionContext& executionContext) const override; +}; +} // namespace nx::core + +SIMPLNX_DEF_FILTER_TRAITS(nx::core, ComputeEquivalentDiameterCirclesFilter, "f841b59f-c176-4eca-be2b-8e9bdee012c6"); +/* LEGACY UUID FOR THIS FILTER 708be082-8b08-4db2-94be-52781ed4d53d */