diff --git a/src/SimplnxReview/Filters/Algorithms/ComputeLocalAverageCAxisMisalignments.cpp b/src/SimplnxReview/Filters/Algorithms/ComputeLocalAverageCAxisMisalignments.cpp index b35869e..11ab098 100644 --- a/src/SimplnxReview/Filters/Algorithms/ComputeLocalAverageCAxisMisalignments.cpp +++ b/src/SimplnxReview/Filters/Algorithms/ComputeLocalAverageCAxisMisalignments.cpp @@ -18,9 +18,11 @@ template > class FindLocalAverageMisalignments { public: - FindLocalAverageMisalignments(const std::atomic_bool& shouldCancel, const Int32Array& featureParentIds, const Float32Array& avgCAxisMisalignments, Int32NeighborList& neighborList, - Float32NeighborList& cAxisMisalignmentList, Int32Array& numFeaturesPerParent, Float32Array& unbiasedLocalCAxisMisalignments, Float32Array& localCAxisMisalignments) + FindLocalAverageMisalignments(const std::atomic_bool& shouldCancel, ThrottledMessageHandler& progressThrottle, const Int32Array& featureParentIds, const Float32Array& avgCAxisMisalignments, + Int32NeighborList& neighborList, Float32NeighborList& cAxisMisalignmentList, Int32Array& numFeaturesPerParent, Float32Array& unbiasedLocalCAxisMisalignments, + Float32Array& localCAxisMisalignments) : m_ShouldCancel(shouldCancel) + , m_Throttle(progressThrottle) , m_FeatureParentIds(featureParentIds) , m_AvgCAxisMisalignments(avgCAxisMisalignments) , m_NeighborList(neighborList) @@ -50,12 +52,14 @@ class FindLocalAverageMisalignments numUnbiasedFeaturesPerParent.resize(numFeatures); } + m_Throttle.reset(numFeatures > 1 ? numFeatures - 1 : 0, "Computing Local Average CAxis Misalignments"); for(usize i = 1; i < numFeatures; i++) { if(m_ShouldCancel) { return {}; } + m_Throttle.updatePercent(i - 1); int32 parentId = m_FeatureParentIds[i]; if constexpr(MisalignmentArguments::CalculatingUnbiasedAverage) @@ -77,12 +81,14 @@ class FindLocalAverageMisalignments } } + m_Throttle.reset(newNumFeatures > 1 ? newNumFeatures - 1 : 0, "Normalizing Parent Misalignments"); for(usize i = 1; i < newNumFeatures; i++) { if(m_ShouldCancel) { return {}; } + m_Throttle.updatePercent(i - 1); if constexpr(MisalignmentArguments::CalculatingBiasedAverage) { @@ -107,6 +113,7 @@ class FindLocalAverageMisalignments private: const std::atomic_bool& m_ShouldCancel; + ThrottledMessageHandler& m_Throttle; // Unmodified Arrays const Int32Array& m_FeatureParentIds; @@ -128,6 +135,7 @@ ComputeLocalAverageCAxisMisalignments::ComputeLocalAverageCAxisMisalignments(Dat , m_InputValues(inputValues) , m_ShouldCancel(shouldCancel) , m_MessageHandler(mesgHandler) +, m_Throttle(mesgHandler) { } @@ -155,15 +163,15 @@ Result<> ComputeLocalAverageCAxisMisalignments::operator()() { if(m_InputValues->CalcUnbiasedAvg) { - return ::FindLocalAverageMisalignments>(getCancel(), featureParentIds, avgCAxisMisalignments, neighborList, cAxisMisalignmentList, numFeaturesPerParent, - unbiasedLocalCAxisMisalignments, localCAxisMisalignments)(); + return ::FindLocalAverageMisalignments>(getCancel(), m_Throttle, featureParentIds, avgCAxisMisalignments, neighborList, cAxisMisalignmentList, + numFeaturesPerParent, unbiasedLocalCAxisMisalignments, localCAxisMisalignments)(); } - return ::FindLocalAverageMisalignments>(getCancel(), featureParentIds, avgCAxisMisalignments, neighborList, cAxisMisalignmentList, numFeaturesPerParent, - unbiasedLocalCAxisMisalignments, localCAxisMisalignments)(); + return ::FindLocalAverageMisalignments>(getCancel(), m_Throttle, featureParentIds, avgCAxisMisalignments, neighborList, cAxisMisalignmentList, + numFeaturesPerParent, unbiasedLocalCAxisMisalignments, localCAxisMisalignments)(); } // Since we validate that at least one of the bool options is true in preflight we know that // by reaching this point in the logic we m_InputValues->CalcUnbiasedAvg must be true - return ::FindLocalAverageMisalignments>(getCancel(), featureParentIds, avgCAxisMisalignments, neighborList, cAxisMisalignmentList, numFeaturesPerParent, - unbiasedLocalCAxisMisalignments, localCAxisMisalignments)(); + return ::FindLocalAverageMisalignments>(getCancel(), m_Throttle, featureParentIds, avgCAxisMisalignments, neighborList, cAxisMisalignmentList, + numFeaturesPerParent, unbiasedLocalCAxisMisalignments, localCAxisMisalignments)(); } diff --git a/src/SimplnxReview/Filters/Algorithms/ComputeLocalAverageCAxisMisalignments.hpp b/src/SimplnxReview/Filters/Algorithms/ComputeLocalAverageCAxisMisalignments.hpp index 13b9e2d..29ad350 100644 --- a/src/SimplnxReview/Filters/Algorithms/ComputeLocalAverageCAxisMisalignments.hpp +++ b/src/SimplnxReview/Filters/Algorithms/ComputeLocalAverageCAxisMisalignments.hpp @@ -8,6 +8,7 @@ #include "simplnx/Parameters/ArraySelectionParameter.hpp" #include "simplnx/Parameters/DataGroupSelectionParameter.hpp" #include "simplnx/Parameters/StringParameter.hpp" +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" namespace nx::core { @@ -49,5 +50,6 @@ class SIMPLNXREVIEW_EXPORT ComputeLocalAverageCAxisMisalignments const ComputeLocalAverageCAxisMisalignmentsInputValues* m_InputValues = nullptr; const std::atomic_bool& m_ShouldCancel; const IFilter::MessageHandler& m_MessageHandler; + ThrottledMessageHandler m_Throttle; }; } // namespace nx::core diff --git a/src/SimplnxReview/Filters/Algorithms/ComputeMicroTextureRegions.cpp b/src/SimplnxReview/Filters/Algorithms/ComputeMicroTextureRegions.cpp index fdb6afd..80b4d8c 100644 --- a/src/SimplnxReview/Filters/Algorithms/ComputeMicroTextureRegions.cpp +++ b/src/SimplnxReview/Filters/Algorithms/ComputeMicroTextureRegions.cpp @@ -2,6 +2,7 @@ #include "simplnx/DataStructure/DataArray.hpp" #include "simplnx/DataStructure/Geometry/ImageGeom.hpp" +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" #include @@ -49,10 +50,17 @@ Result<> ComputeMicroTextureRegions::operator()() std::for_each(featureIds.begin(), featureIds.end(), [µTextureRegionNumCells](const int32 id) mutable { microTextureRegionNumCells[id].inc(); }); + ThrottledMessageHandler progressThrottle(m_MessageHandler); + progressThrottle.reset(zPoints, "Finding MicroTexture Region Bounds"); + float32 x, y, z; usize zStride, yStride; for(usize i = 0; i < zPoints; i++) { + if(m_ShouldCancel) + { + return {}; + } zStride = i * xPoints * yPoints; for(usize j = 0; j < yPoints; j++) { @@ -89,11 +97,17 @@ Result<> ComputeMicroTextureRegions::operator()() } } } + progressThrottle.updateCount(i + 1); } auto& microTextureRegionFractionOccupied = m_DataStructure.getDataRefAs(m_InputValues->MicroTextureRegionFractionOccupiedArrayPath); + progressThrottle.reset(numMicroTextureRegions > 1 ? numMicroTextureRegions - 1 : 0, "Computing MicroTexture Region Fractions"); for(usize i = 1; i < numMicroTextureRegions; i++) { + if(m_ShouldCancel) + { + return {}; + } float32 xLength = (microTextureRegionXMaxs[i] - microTextureRegionXMins[i]) + spacing[0]; float32 yLength = (microTextureRegionYMaxs[i] - microTextureRegionYMins[i]) + spacing[1]; if(zPoints == 1) @@ -107,6 +121,7 @@ Result<> ComputeMicroTextureRegions::operator()() float32 rectangleVolume = xLength * yLength; microTextureRegionFractionOccupied[i] = (static_cast(microTextureRegionNumCells[i]) * spacing[0] * spacing[1]) / rectangleVolume; } + progressThrottle.updateCount(i); } return {}; diff --git a/src/SimplnxReview/Filters/Algorithms/ComputeSaltykovSizes.cpp b/src/SimplnxReview/Filters/Algorithms/ComputeSaltykovSizes.cpp index 080edf3..2c6f221 100644 --- a/src/SimplnxReview/Filters/Algorithms/ComputeSaltykovSizes.cpp +++ b/src/SimplnxReview/Filters/Algorithms/ComputeSaltykovSizes.cpp @@ -2,6 +2,7 @@ #include "simplnx/DataStructure/DataArray.hpp" #include "simplnx/DataStructure/DataGroup.hpp" +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" #include @@ -126,8 +127,17 @@ Result<> ComputeSaltykovSizes::operator()() // the feature eq dia. But, it is important to note that the Saltykov eq dia // is not a direct transformation of the particular eq dia that it is matched // up with + ThrottledMessageHandler progressThrottle(m_MessageHandler); + usize fittingAttempt = 0; while(saltykovLength != numFeatures - 1) { + if(m_ShouldCancel) + { + return {}; + } + fittingAttempt++; + progressThrottle.queueMessage("Fitting Saltykov bins: attempt {}, {} bins", fittingAttempt, numberOfBins); + // find the bin length binLength = maxEqDia / static_cast(numberOfBins - 1); @@ -260,8 +270,14 @@ Result<> ComputeSaltykovSizes::operator()() std::sort(saltykovEquivalentDiameters.begin(), saltykovEquivalentDiameters.end(), std::less<>()); // this nested loop matches the Saltykov eq dia's with the feature eq dia's in ascending order + progressThrottle.reset(numFeatures > 1 ? numFeatures - 1 : 0, "Matching Saltykov Diameters"); for(usize i = 1; i < numFeatures; i++) { + if(m_ShouldCancel) + { + return {}; + } + progressThrottle.updatePercent(i - 1); for(usize j = 1; j < numFeatures; j++) { if(equivalentDiameters[j] == currentMinimum) diff --git a/src/SimplnxReview/Filters/Algorithms/InterpolateValuesToUnstructuredGrid.cpp b/src/SimplnxReview/Filters/Algorithms/InterpolateValuesToUnstructuredGrid.cpp index ce796d3..9dc6659 100644 --- a/src/SimplnxReview/Filters/Algorithms/InterpolateValuesToUnstructuredGrid.cpp +++ b/src/SimplnxReview/Filters/Algorithms/InterpolateValuesToUnstructuredGrid.cpp @@ -37,25 +37,22 @@ class CalculateClosestVerticesImpl void generate(size_t start, size_t end) const { - auto startTime = std::chrono::steady_clock::now(); usize counter = 0; - usize increment = (end - start) / 100; + // A count stride keeps the seam out of the per-vertex hot path. The throttle behind the seam + // decides when a message is actually due, so this loop never reads the clock. + const usize increment = std::max(1, (end - start) / 100); for(usize destVertexId = start; destVertexId < end; destVertexId++) { if(m_ShouldCancel) { + m_Filter->sendThreadSafeProgressMessage(counter); return; } if(counter > increment) { - auto now = std::chrono::steady_clock::now(); - if(std::chrono::duration_cast(now - startTime).count() > 1000) - { - m_Filter->sendThreadSafeProgressMessage(counter); - counter = 0; - startTime = std::chrono::steady_clock::now(); - } + m_Filter->sendThreadSafeProgressMessage(counter); + counter = 0; } Vec3 destVertexCoord = m_DestGeometry.getVertexCoordinate(destVertexId); @@ -112,6 +109,7 @@ InterpolateValuesToUnstructuredGrid::InterpolateValuesToUnstructuredGrid(DataStr , m_InputValues(inputValues) , m_ShouldCancel(shouldCancel) , m_MessageHandler(mesgHandler) +, m_Throttle(mesgHandler) { } @@ -134,12 +132,17 @@ Result<> InterpolateValuesToUnstructuredGrid::operator()() // set up thread-safe messenger m_TotalElements = destGeometry.getNumberOfVertices(); + m_Throttle.reset(m_TotalElements, "Calculating Closest Vertices"); // Parallel algorithm to calculate closest vertices ParallelDataAlgorithm dataAlg; dataAlg.setRange(0ULL, static_cast(destGeometry.getNumberOfVertices())); dataAlg.execute(CalculateClosestVerticesImpl(this, srcGeometry, destGeometry, closestSrcIds, m_MessageHandler, m_ShouldCancel)); - m_MessageHandler(IFilter::Message::Type::Info, "Calculating Closest Vertices || 100%"); + if(m_ShouldCancel) + { + return {}; + } + m_MessageHandler.sendProgressCount("Calculating Closest Vertices", m_TotalElements, m_TotalElements); DataPath interpolatedAttrMatrixPath; if(m_InputValues->UseExistingAttrMatrix) @@ -151,20 +154,24 @@ Result<> InterpolateValuesToUnstructuredGrid::operator()() interpolatedAttrMatrixPath = m_InputValues->DestinationGeomPath.createChildPath(m_InputValues->CreatedAttrMatrixName); } + ProgressEstimator arrayEstimator; for(usize i = 0; i < m_InputValues->InputDataPaths.size(); i++) { const auto& dataPath = m_InputValues->InputDataPaths[i]; - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Interpolating \"{}\" Array Values || {}/{}", dataPath.getTargetName(), i + 1, m_InputValues->InputDataPaths.size())); if(m_ShouldCancel) { return {}; } + m_MessageHandler.sendInfoMessage(fmt::format("Interpolating \"{}\" Array Values", dataPath.getTargetName())); + const auto& srcDataArray = m_DataStructure.getDataRefAs(dataPath); auto& destDataArray = m_DataStructure.getDataRefAs(interpolatedAttrMatrixPath.createChildPath(dataPath.getTargetName())); ExecuteDataFunction(ExecuteInterpolation{}, srcDataArray.getDataType(), srcDataArray, destDataArray, closestSrcIds); + + m_MessageHandler.sendProgressCount("Interpolating Array Values", i + 1, m_InputValues->InputDataPaths.size(), arrayEstimator.estimate(i + 1, m_InputValues->InputDataPaths.size())); } return {}; @@ -174,18 +181,5 @@ Result<> InterpolateValuesToUnstructuredGrid::operator()() void InterpolateValuesToUnstructuredGrid::sendThreadSafeProgressMessage(usize counter) { std::lock_guard guard(m_ProgressMessage_Mutex); - - m_ProgressCounter += counter; - auto now = std::chrono::steady_clock::now(); - if(std::chrono::duration_cast(now - m_InitialPoint).count() > 1000) - { - return; - } - - auto progressInt = static_cast((static_cast(m_ProgressCounter) / static_cast(m_TotalElements)) * 100.0f); - std::string ss = fmt::format("Calculating Closest Vertices || {}%", progressInt); - m_MessageHandler(IFilter::Message::Type::Info, ss); - - m_LastProgressInt = progressInt; - m_InitialPoint = std::chrono::steady_clock::now(); + m_Throttle.incrementPercent(counter); } diff --git a/src/SimplnxReview/Filters/Algorithms/InterpolateValuesToUnstructuredGrid.hpp b/src/SimplnxReview/Filters/Algorithms/InterpolateValuesToUnstructuredGrid.hpp index 47fdaa7..412c71d 100644 --- a/src/SimplnxReview/Filters/Algorithms/InterpolateValuesToUnstructuredGrid.hpp +++ b/src/SimplnxReview/Filters/Algorithms/InterpolateValuesToUnstructuredGrid.hpp @@ -13,6 +13,8 @@ #include "simplnx/Parameters/NumberParameter.hpp" #include "simplnx/Parameters/VectorParameter.hpp" #include "simplnx/Utilities/ImageRotationUtilities.hpp" +#include "simplnx/Utilities/ProgressEstimator.hpp" +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" #include @@ -59,11 +61,9 @@ class SIMPLNXREVIEW_EXPORT InterpolateValuesToUnstructuredGrid const IFilter::MessageHandler& m_MessageHandler; // Thread safe Progress Message - std::chrono::steady_clock::time_point m_InitialPoint = std::chrono::steady_clock::now(); mutable std::mutex m_ProgressMessage_Mutex; - size_t m_TotalElements = 0; - size_t m_ProgressCounter = 0; - size_t m_LastProgressInt = 0; + usize m_TotalElements = 0; + ThrottledMessageHandler m_Throttle; }; } // namespace nx::core diff --git a/src/SimplnxReview/Filters/Algorithms/MergeColonies.cpp b/src/SimplnxReview/Filters/Algorithms/MergeColonies.cpp index bc1462b..02c76b6 100644 --- a/src/SimplnxReview/Filters/Algorithms/MergeColonies.cpp +++ b/src/SimplnxReview/Filters/Algorithms/MergeColonies.cpp @@ -5,7 +5,7 @@ #include "simplnx/DataStructure/DataArray.hpp" #include "simplnx/DataStructure/NeighborList.hpp" #include "simplnx/Utilities/Math/GeometryMath.hpp" -#include "simplnx/Utilities/MessageHelper.hpp" +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" #include "EbsdLib/Core/EbsdLibConstants.h" #include "EbsdLib/Core/Orientation.hpp" @@ -14,6 +14,13 @@ #include using namespace nx::core; + +namespace +{ +// Cells are far too small a unit to report or poll cancellation on individually. The stride is the +// outer work unit for the whole-volume passes below. +constexpr usize k_ProgressCellStride = 65536; +} // namespace using LaueOpsShPtrType = std::shared_ptr; using LaueOpsContainer = std::vector; @@ -146,8 +153,7 @@ bool MergeColonies::growGrouping(int32_t referenceFeature, int32_t neighborFeatu // ----------------------------------------------------------------------------- Result<> MergeColonies::execute() { - MessageHelper messageHelper(m_MessageHandler); - ThrottledMessenger throttledMessenger = messageHelper.createThrottledMessenger(); + ThrottledMessageHandler progressThrottle(m_MessageHandler); NeighborList& featureNeighborListRef = m_DataStructure.getDataRefAs>(m_InputValues->ContiguousNeighborListArrayPath); NeighborList* nonContigNeighListPtr = nullptr; @@ -252,7 +258,7 @@ Result<> MergeColonies::execute() } } - throttledMessenger.sendThrottledMessage([&]() { return fmt::format("Parent Count: {}", parentCount); }); + progressThrottle.queueMessage("Parent Count: {}", parentCount); } groupList.clear(); } @@ -287,8 +293,18 @@ Result<> MergeColonies::operator()() int32 numParents = 0; usize totalPoints = featureIds.getNumberOfTuples(); + ThrottledMessageHandler cellThrottle(m_MessageHandler); + cellThrottle.reset(totalPoints, "Mapping Cells to Parents"); for(usize k = 0; k < totalPoints; k++) { + if(k % k_ProgressCellStride == 0) + { + if(m_ShouldCancel) + { + return {}; + } + cellThrottle.updatePercent(k); + } int32 featurename = featureIds[k]; cellParentIds[k] = m_FeatureParentIds[featurename]; if(m_FeatureParentIds[featurename] > numParents) @@ -298,13 +314,13 @@ Result<> MergeColonies::operator()() } numParents += 1; - m_MessageHandler({IFilter::Message::Type::Info, "Characterizing Colonies Starting"}); + m_MessageHandler.sendInfoMessage("Characterizing Colonies Starting"); characterize_colonies(); - m_MessageHandler({IFilter::Message::Type::Info, "Characterizing Colonies Complete"}); + m_MessageHandler.sendInfoMessage("Characterizing Colonies Complete"); if(m_InputValues->RandomizeParentIds) { - m_MessageHandler({IFilter::Message::Type::Info, "Randomizing Parent Ids...."}); + m_MessageHandler.sendInfoMessage("Randomizing Parent Ids...."); // Generate all the numbers up front const int32 rangeMin = 1; const int32 rangeMax = numParents - 1; @@ -324,7 +340,7 @@ Result<> MergeColonies::operator()() int32 r = 0; int32 temp = 0; - m_MessageHandler({IFilter::Message::Type::Info, "Shuffle elements ...."}); + m_MessageHandler.sendInfoMessage("Shuffle elements ...."); //--- Shuffle elements by randomly exchanging each with one other. for(int32 i = 1; i < numParents; i++) { @@ -338,10 +354,23 @@ Result<> MergeColonies::operator()() pid[r] = temp; } - m_MessageHandler({IFilter::Message::Type::Info, "Adjusting Feature Ids Array...."}); + m_MessageHandler.sendInfoMessage("Adjusting Feature Ids Array...."); // Now adjust all the FeatureId values for each Voxel + cellThrottle.reset(totalPoints, "Adjusting Feature Ids"); + // This pass rewrites the cell parent id and the feature-level parent id for the same feature. + // Returning part way leaves cellParentIds inconsistent with m_FeatureParentIds, and nothing + // restores either, so the remap runs to completion once started. It is one cheap pass over the + // cells; cancellation is honoured before it begins. + if(m_ShouldCancel) + { + return {}; + } for(usize i = 0; i < totalPoints; ++i) { + if(i % k_ProgressCellStride == 0) + { + cellThrottle.updatePercent(i); + } cellParentIds[i] = pid[cellParentIds[i]]; m_FeatureParentIds[featureIds[i]] = cellParentIds[i]; }