diff --git a/CMakeLists.txt b/CMakeLists.txt index 10680552e8..62526cc71f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -187,7 +187,6 @@ find_package(span-lite CONFIG REQUIRED) find_package(Eigen3 REQUIRED) find_package(boost_mp11 CONFIG REQUIRED) find_package(nod CONFIG REQUIRED) -find_package(spdlog CONFIG REQUIRED) #------------------------------------------------------------------------------ # Find the Image IO libraries @@ -279,7 +278,6 @@ target_link_libraries(simplnx HDF5::HDF5 Boost::mp11 nod::nod - spdlog::spdlog TIFF::TIFF ) @@ -539,7 +537,6 @@ set(SIMPLNX_HDRS ${SIMPLNX_SOURCE_DIR}/Pipeline/Messaging/NodeRemovedMessage.hpp ${SIMPLNX_SOURCE_DIR}/Pipeline/Messaging/NodeStatusMessage.hpp ${SIMPLNX_SOURCE_DIR}/Pipeline/Messaging/OutputRenamedMessage.hpp - ${SIMPLNX_SOURCE_DIR}/Pipeline/Messaging/PipelineFilterMessage.hpp ${SIMPLNX_SOURCE_DIR}/Pipeline/Messaging/PipelineNodeMessage.hpp ${SIMPLNX_SOURCE_DIR}/Pipeline/Messaging/PipelineNodeObserver.hpp ${SIMPLNX_SOURCE_DIR}/Pipeline/Messaging/RenamedMessage.hpp @@ -567,7 +564,7 @@ set(SIMPLNX_HDRS ${SIMPLNX_SOURCE_DIR}/Utilities/HistogramUtilities.hpp ${SIMPLNX_SOURCE_DIR}/Utilities/MaskCompareUtilities.hpp ${SIMPLNX_SOURCE_DIR}/Utilities/MemoryUtilities.hpp - ${SIMPLNX_SOURCE_DIR}/Utilities/MessageHelper.hpp + ${SIMPLNX_SOURCE_DIR}/Utilities/ThrottledMessageHandler.hpp ${SIMPLNX_SOURCE_DIR}/Utilities/StringUtilities.hpp ${SIMPLNX_SOURCE_DIR}/Utilities/StringInterpretationUtilities.hpp ${SIMPLNX_SOURCE_DIR}/Utilities/IntersectionUtilities.hpp @@ -761,7 +758,6 @@ set(SIMPLNX_SRCS ${SIMPLNX_SOURCE_DIR}/Pipeline/Messaging/NodeRemovedMessage.cpp ${SIMPLNX_SOURCE_DIR}/Pipeline/Messaging/NodeStatusMessage.cpp ${SIMPLNX_SOURCE_DIR}/Pipeline/Messaging/OutputRenamedMessage.cpp - ${SIMPLNX_SOURCE_DIR}/Pipeline/Messaging/PipelineFilterMessage.cpp ${SIMPLNX_SOURCE_DIR}/Pipeline/Messaging/PipelineNodeMessage.cpp ${SIMPLNX_SOURCE_DIR}/Pipeline/Messaging/PipelineNodeObserver.cpp ${SIMPLNX_SOURCE_DIR}/Pipeline/Messaging/RenamedMessage.cpp @@ -782,7 +778,7 @@ set(SIMPLNX_SRCS ${SIMPLNX_SOURCE_DIR}/Utilities/DataStoreUtilities.cpp ${SIMPLNX_SOURCE_DIR}/Utilities/MaskCompareUtilities.cpp ${SIMPLNX_SOURCE_DIR}/Utilities/MemoryUtilities.cpp - ${SIMPLNX_SOURCE_DIR}/Utilities/MessageHelper.cpp + ${SIMPLNX_SOURCE_DIR}/Utilities/ThrottledMessageHandler.cpp ${SIMPLNX_SOURCE_DIR}/Utilities/IParallelAlgorithm.cpp ${SIMPLNX_SOURCE_DIR}/Utilities/ParallelDataAlgorithm.cpp ${SIMPLNX_SOURCE_DIR}/Utilities/ParallelData2DAlgorithm.cpp diff --git a/docs/PortingFilters.md b/docs/PortingFilters.md index ad4470c92e..51c9c64b76 100644 --- a/docs/PortingFilters.md +++ b/docs/PortingFilters.md @@ -245,104 +245,212 @@ dataAlg.execute(::FilterNameImpl(object, argument)); With out of core functionality on the way, it is now a requirement for each and every filter to have progress updates and checks for cancel. This section shows threadsafe progress updating and message structuring. -### Serial Messaging API +### Sending Messages -Modern API offers a `MessageHelper` wrapper class that offers an object that has assorted helper functions, such as a throttled messenger. +Every filter's algorithm class holds a `const IFilter::MessageHandler& m_MessageHandler`. Send through +it directly using the named methods, which say what kind of message you are sending: -Include it with the following: +```cpp +m_MessageHandler.sendInfoMessage("Initializing working grid"); +m_MessageHandler.sendWarningMessage(fmt::format("Phase {} has an unknown crystal structure", phase)); +m_MessageHandler.sendErrorMessage("Could not open file"); +m_MessageHandler.sendDebugMessage("cache miss"); +``` + +These are sent immediately and are never dropped. Use them for section headers and one-time +statements. **Do not call them from a loop** — an unthrottled message per iteration is expensive and +floods the log. + +For a one-off progress report, pick the call that matches what you want the user to read: ```cpp -#include "simplnx/Utilities/MessageHelper.hpp" +// counts, when the numbers mean something to a user: "Reading slices: 3/200" +m_MessageHandler.sendProgressCount("Reading slices", slice, totalSlices); + +// percentage, when the counts are too large to be readable: "Analyzing voxels: 37.81%" +m_MessageHandler.sendProgressPercent("Analyzing voxels", voxel, totalVoxels); ``` -You create it in the algorithms execution body like this +Both also deliver the percentage as a separate numeric field, which is what drives the progress bar in +DREAM3D-NX and the percent column in `nxrunner`. Choosing between them records your intent for whoever +maintains the filter next, so prefer them over assembling the text yourself. + +### Throttled Messaging In A Loop + +`ThrottledMessageHandler` rate-limits messages so a tight loop can report without measurable cost. The +loop body only reads an atomic flag, which is roughly 60x cheaper than reading the clock on every +iteration, and the message is only formatted when one is actually due. ```cpp -MessageHelper messageHelper(m_MessageHandler); +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" ``` -To send a message immediately to the console, use the following syntax: +Create one in the algorithm's execution body. It emits at most one message per second by default: ```cpp -messageHelper.sendMessage("Header Here:"); +ThrottledMessageHandler throttle(m_MessageHandler); +ThrottledMessageHandler throttle(m_MessageHandler, std::chrono::milliseconds(2000)); // or a custom interval ``` -In practice this should be used to print a section header for updates or a one-time message. **Avoid using this as a progress messenger in a loop since it can incur a significant compute-cost penalty in the form of excessive output OR brach check cost** +Report progress with the same two intents as above, supplying the label and denominator at the call +site: + +```cpp +for(usize i = 0; i < totalTuples; i++) +{ + if(m_ShouldCancel) + { + return {}; + } + // ... work ... + throttle.updateCount(" - Searching", i, totalTuples); // " - Searching: 3400/10000" + throttle.updatePercent(" - Searching", i, totalTuples); // " - Searching: 34.10%" +} +``` -For progress messaging a throttled messenger class is provided. To initialize it do the following: +For status text that is not a progress fraction, use `queueMessage()`. The format string is checked at +compile time and the arguments are only formatted when a message is due: ```cpp -ThrottledMessenger throttledMessenger = messageHelper.createThrottledMessenger(); +throttle.queueMessage(" Iteration {}: {} voxels remaining", iteration, count); ``` -The throttled messenger will print every 1000 milliseconds (1 second) by default, to change this initialize it as follows: +Note that `queueMessage()` evaluates its *arguments* on every call and defers only the formatting. If +building the message is itself expensive, or has a side effect that must happen only when a message is +sent, pass a functor instead so the whole body is deferred: ```cpp -ThrottledMessenger throttledMessenger = messageHelper.createThrottledMessenger(static_cast(number_in_milliseconds)); +throttle.queueMessage([&]() { + auto now = std::chrono::steady_clock::now(); // runs only when a message is due + lastReportTime = now; + return fmt::format("Processed {} triangles, est. {} remaining", count, estimate); +}); ``` -Then provide the statement to be printed wrapped in a lambda as follows: +If you cannot pass an absolute position — for example you only know how many items you just finished — +set the denominator and label once, then accumulate: ```cpp -throttledMessenger.sendThrottledMessage([&]() { return fmt::format(" - Your Message || {:.2f}% Complete", CalculatePercentComplete(current_position, max_position)); +throttle.reset(totalTuples, "Converting Orientations"); +throttle.incrementCount(chunkSize); // or incrementPercent(chunkSize) ``` -Here is a complete MRE: +Call `reset()` again between phases; it also reopens the gate so each phase reports immediately. + +Here is a complete example: ```cpp -#include "simplnx/Utilities/MessageHelper.hpp" +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" ... -/// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- Result<> SomeAlgorithmClass::operator()() { - // Create wrapper messing class - MessageHelper messageHelper(m_MessageHandler); - - // Send Header - messageHelper.sendMessage("Parsing Input Data:"); - + m_MessageHandler.sendInfoMessage("Parsing Input Data:"); - ThrottledMessenger throttledMessenger = messageHelper.createThrottledMessenger(static_cast(2000)); + ThrottledMessageHandler throttle(m_MessageHandler); for(usize i = 0; i < 10000; i++) { + if(m_ShouldCancel) + { + return {}; + } // Do stuff - throttledMessenger.sendThrottledMessage([&]() { return fmt::format(" - Searching || {:.2f}% Complete", CalculatePercentComplete(i, 1000)); + throttle.updatePercent(" - Searching", i, 10000); } + + return {}; } ``` -This would produce something similar to the following output +This produces output similar to the following: ```console Parsing Input Data: - - Searching || 1.2% Complete - - Searching || 15.7% Complete - - Searching || 34.1% Complete + - Searching: 1.20% + - Searching: 15.70% + - Searching: 34.10% ``` ### ThreadSafe Progress Messaging -!!! THIS SECTION IS OUT OF DATE - -This is an example that aims to reduce the number of times a mutex lock is called. - -> void updateThreadSafeProgress(size_t 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_InitialTime).count() > 1000) // every second update -> { -> size_t progressInt = static_cast((static_cast(m_ProgressCounter) / m_TotalElements) * 100.0); -> std::string progressMessage = "Calculating... "; -> m_MessageHandler(IFilter::ProgressMessage{IFilter::Message::Type::Progress, progressMessage, static_cast(progressInt)}); -> m_InitialTime = std::chrono::steady_clock::now(); -> } -> } - -This function should avoid being called too many times in a thread as it would significantly slow it down. +`ThrottledMessageHandler` is **not** internally thread-safe, and it owns a thread, so do not create one +per worker. Once an algorithm starts its own parallel work, the algorithm owns a single throttle behind +a mutex and exposes a thread-safe seam. Workers call the seam and never touch an +`IFilter::MessageHandler` directly — a worker holding a message handler is a bug. + +In the algorithm header: + +```cpp +class SomeAlgorithm +{ +public: + /** + * @brief Thread-safe progress update. Safe to call from ParallelDataAlgorithm workers. + * @param counter Items completed since the previous call + */ + void sendThreadSafeProgressMessage(usize counter); + +private: + const IFilter::MessageHandler& m_MessageHandler; + mutable std::mutex m_ProgressMessage_Mutex; + ThrottledMessageHandler m_Throttle; // non-movable: initialise in the constructor +}; +``` + +In the source, initialise the throttle in the constructor's initialiser list, set the denominator +before dispatch, and keep the seam to one line: + +```cpp +SomeAlgorithm::SomeAlgorithm(DataStructure& dataStructure, const IFilter::MessageHandler& mesgHandler, ...) +: m_MessageHandler(mesgHandler) +, m_Throttle(mesgHandler) +{ +} + +void SomeAlgorithm::sendThreadSafeProgressMessage(usize counter) +{ + std::lock_guard guard(m_ProgressMessage_Mutex); + m_Throttle.incrementCount(counter); +} + +Result<> SomeAlgorithm::operator()() +{ + m_Throttle.reset(totalElements, "Converting Orientations"); + + ParallelDataAlgorithm dataAlg; + dataAlg.setRange(0, totalElements); + dataAlg.execute(SomeAlgorithmImpl(this, ...)); // the worker holds the algorithm pointer + return {}; +} +``` + +The worker then reports through the algorithm: + +```cpp +void operator()(const Range& range) const +{ + for(usize i = range.min(); i < range.max(); i++) + { + // ... work ... + m_Filter->sendThreadSafeProgressMessage(1); + } +} +``` + +Do not call the seam on every iteration of a very tight loop, because it takes the mutex each time. +Pre-gate on a count so the lock is taken roughly a hundred times per worker instead: + +```cpp +const usize progressIncrement = std::max(totalPoints / 100ULL, 1ULL); +if(i % progressIncrement == 0) +{ + m_Filter->sendThreadSafeProgressMessage(progressIncrement); +} +``` + +If each worker needs its own message text — for example when every worker handles a different data +array — give the seam a `const std::string&` parameter and forward it to +`m_Throttle.trySendMessage(message)` instead of accumulating a shared counter. ### Message Structuring diff --git a/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Common/ITKProgressObserver.hpp b/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Common/ITKProgressObserver.hpp index 8f86fee871..92e143d2aa 100644 --- a/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Common/ITKProgressObserver.hpp +++ b/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Common/ITKProgressObserver.hpp @@ -74,7 +74,7 @@ class ProgressObserver : public itk::Command ss = fmt::format("{} : {}", m_MessagePrefix, progressStr); } - m_MessageHandler(nx::core::IFilter::Message::Type::Info, ss); + m_MessageHandler.sendInfoMessage(ss); m_StartTime = std::chrono::steady_clock::now(); } } diff --git a/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/Algorithms/ITKImportFijiMontage.cpp b/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/Algorithms/ITKImportFijiMontage.cpp index ccfec212cf..d2caafd2bc 100644 --- a/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/Algorithms/ITKImportFijiMontage.cpp +++ b/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/Algorithms/ITKImportFijiMontage.cpp @@ -321,7 +321,7 @@ FijiCache& ITKImportFijiMontage::getCache() // ----------------------------------------------------------------------------- void ITKImportFijiMontage::sendUpdate(const std::string& message) { - m_MessageHandler({IFilter::Message::Type::Info, message}); + m_MessageHandler.sendInfoMessage(message); } // ----------------------------------------------------------------------------- diff --git a/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKImageWriterFilter.cpp b/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKImageWriterFilter.cpp index e42aa7f220..0d7f0aaf4a 100644 --- a/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKImageWriterFilter.cpp +++ b/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKImageWriterFilter.cpp @@ -402,7 +402,7 @@ Result<> ITKImageWriterFilter::executeImpl(DataStructure& dataStructure, const A return copyResult; } const fs::path outputFilePath = cxITKImageWriterFilter::GenerateOutputFilePath(filePath, slice + indexOffset, dims.getZ(), totalDigits, fillChar); - messageHandler(fmt::format("Writing file {} of {}: \"{}\"", slice + 1, dims.getZ(), outputFilePath.string())); + messageHandler.sendInfoMessage(fmt::format("Writing file {} of {}: \"{}\"", slice + 1, dims.getZ(), outputFilePath.string())); Result<> result = cxITKImageWriterFilter::SaveImageData(outputFilePath, *sliceData, newImageGeom); if(result.invalid()) { @@ -433,7 +433,7 @@ Result<> ITKImageWriterFilter::executeImpl(DataStructure& dataStructure, const A return copyResult; } const fs::path outputFilePath = cxITKImageWriterFilter::GenerateOutputFilePath(filePath, slice + indexOffset, dims.getY(), totalDigits, fillChar); - messageHandler(fmt::format("Writing file {} of {}: \"{}\"", slice + 1, dims.getY(), outputFilePath.string())); + messageHandler.sendInfoMessage(fmt::format("Writing file {} of {}: \"{}\"", slice + 1, dims.getY(), outputFilePath.string())); Result<> result = cxITKImageWriterFilter::SaveImageData(outputFilePath, *sliceData, newImageGeom); if(result.invalid()) { @@ -464,7 +464,7 @@ Result<> ITKImageWriterFilter::executeImpl(DataStructure& dataStructure, const A return copyResult; } const fs::path outputFilePath = cxITKImageWriterFilter::GenerateOutputFilePath(filePath, slice + indexOffset, dims.getX(), totalDigits, fillChar); - messageHandler(fmt::format("Writing file {} of {}: \"{}\"", slice + 1, dims.getX(), outputFilePath.string())); + messageHandler.sendInfoMessage(fmt::format("Writing file {} of {}: \"{}\"", slice + 1, dims.getX(), outputFilePath.string())); Result<> result = cxITKImageWriterFilter::SaveImageData(outputFilePath, *sliceData, newImageGeom); if(result.invalid()) { diff --git a/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKImportImageStackFilter.cpp b/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKImportImageStackFilter.cpp index 38e61f3712..3fbada2020 100644 --- a/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKImportImageStackFilter.cpp +++ b/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKImportImageStackFilter.cpp @@ -175,7 +175,7 @@ Result<> ReadImageStack(DataStructure& dataStructure, const DataPath& imageGeomP for(usize i = startSlice; i <= endSlice; i++) { const std::string& filePath = files[i]; - messageHandler(IFilter::Message::Type::Info, fmt::format("Importing: {}", filePath)); + messageHandler.sendInfoMessage(fmt::format("Importing: {}", filePath)); DataStructure importedDataStructure; { diff --git a/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKMhaFileReaderFilter.cpp b/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKMhaFileReaderFilter.cpp index 2c64cf9b63..7fdf733fbd 100644 --- a/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKMhaFileReaderFilter.cpp +++ b/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKMhaFileReaderFilter.cpp @@ -424,7 +424,7 @@ Result<> ITKMhaFileReaderFilter::executeImpl(DataStructure& dataStructure, const Result<> mainResult; // Execute the ImageFileReader filter - messageHandler(fmt::format("Reading image file '{}'...", fileNamePath.string())); + messageHandler.sendInfoMessage(fmt::format("Reading image file '{}'...", fileNamePath.string())); { DataStructure importedDataStructure; const ITKImageReaderFilter imageReaderFilter; @@ -442,7 +442,7 @@ Result<> ITKMhaFileReaderFilter::executeImpl(DataStructure& dataStructure, const const auto& srcDataArray = importedDataStructure.getDataRefAs(imageDataArrayPath); const auto& destDataArray = dataStructure.getDataRefAs(imageDataArrayPath); - messageHandler(fmt::format("Copying image data to destination array '{}'...", destDataArray.getName())); + messageHandler.sendInfoMessage(fmt::format("Copying image data to destination array '{}'...", destDataArray.getName())); Result<> copyResult = ExecuteDataFunction(CopyImageDataFunctor{}, srcDataArray.getDataType(), importedDataStructure, dataStructure, imageDataArrayPath, fileNamePath); if(copyResult.invalid()) @@ -455,7 +455,7 @@ Result<> ITKMhaFileReaderFilter::executeImpl(DataStructure& dataStructure, const if(applyImageTransformation || saveImageTransformationAsArray) { - messageHandler(fmt::format("Reading transformation matrix from image file '{}'...", fileNamePath.string())); + messageHandler.sendInfoMessage(fmt::format("Reading transformation matrix from image file '{}'...", fileNamePath.string())); MHAHeaderInfo headerInfo = readMhaHeader(fileNamePath.string(), transposeTransform); if(!headerInfo.Errors.empty()) @@ -474,7 +474,7 @@ Result<> ITKMhaFileReaderFilter::executeImpl(DataStructure& dataStructure, const if(applyImageTransformation) { const auto& imageGeom = dataStructure.getDataRefAs(imageGeomPath); - messageHandler(fmt::format("Applying transformation matrix to image geometry '{}'...", imageGeom.getName())); + messageHandler.sendInfoMessage(fmt::format("Applying transformation matrix to image geometry '{}'...", imageGeom.getName())); auto* filterListPtr = Application::Instance()->getFilterList(); auto applyTransformationToGeometryFilter = filterListPtr->createFilter(k_ApplyTransformationToGeometryFilterHandle); if(applyTransformationToGeometryFilter == nullptr) diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/AlignSectionsMisorientation.cpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/AlignSectionsMisorientation.cpp index 83a59b2db1..d72995ba4a 100644 --- a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/AlignSectionsMisorientation.cpp +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/AlignSectionsMisorientation.cpp @@ -79,7 +79,7 @@ Result<> AlignSectionsMisorientation::findShifts(std::vector& xShifts, const auto halfDim1 = static_cast(dims[1] * 0.5f); double deg2Rad = (nx::core::numbers::pi / 180.0); - ThrottledMessenger throttledMessenger = getMessageHelper().createThrottledMessenger(); + ThrottledMessageHandler throttledMessenger(getMessageHandler()); if(m_InputValues->StoreAlignmentShifts) { auto& slicesStore = m_DataStructure.getDataAs(m_InputValues->SlicesArrayPath)->getDataStoreRef(); @@ -92,7 +92,7 @@ Result<> AlignSectionsMisorientation::findShifts(std::vector& xShifts, { return {}; } - throttledMessenger.sendThrottledMessage([&]() { return fmt::format("Determining Shifts || {:.2f}% Complete", CalculatePercentComplete(iter, dims[2])); }); + throttledMessenger.updatePercent("Determining Shifts", iter, dims[2]); if(getCancel()) { return {}; @@ -200,7 +200,7 @@ Result<> AlignSectionsMisorientation::findShifts(std::vector& xShifts, // Loop over the Z Direction for(int64_t iter = 1; iter < dims[2]; iter++) { - throttledMessenger.sendThrottledMessage([&]() { return fmt::format("Determining Shifts || {:.2f}% Complete", CalculatePercentComplete(iter, dims[2])); }); + throttledMessenger.updatePercent("Determining Shifts", iter, dims[2]); if(getCancel()) { return {}; diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/AlignSectionsMutualInformation.cpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/AlignSectionsMutualInformation.cpp index 2df4c1ac5d..de7070e27b 100644 --- a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/AlignSectionsMutualInformation.cpp +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/AlignSectionsMutualInformation.cpp @@ -95,7 +95,7 @@ Result<> AlignSectionsMutualInformation::findShifts(std::vector& xShifts, { return {}; } - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Determining Shifts: Slice {}/{} complete", iter, dims[2])); + m_MessageHandler.sendInfoMessage(fmt::format("Determining Shifts: Slice {}/{} complete", iter, dims[2])); float32 minDisorientation = std::numeric_limits::max(); int64 slice = (dims[2] - 1) - iter; @@ -217,7 +217,7 @@ Result<> AlignSectionsMutualInformation::findShifts(std::vector& xShifts, { for(int64 iter = 1; iter < dims[2]; iter++) { - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Determining Shifts: Slice {}/{} complete", iter, dims[2])); + m_MessageHandler.sendInfoMessage(fmt::format("Determining Shifts: Slice {}/{} complete", iter, dims[2])); float32 minDisorientation = std::numeric_limits::max(); int64 slice = (dims[2] - 1) - iter; @@ -359,7 +359,7 @@ void AlignSectionsMutualInformation::formFeaturesSections(std::vector& mi for(int64_t slice = 0; slice < dims[2]; slice++) { - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Identifying Features: Slice {}/{} complete", slice, dims[2])); + m_MessageHandler.sendInfoMessage(fmt::format("Identifying Features: Slice {}/{} complete", slice, dims[2])); int64 startPoint = slice * dims[0] * dims[1]; int64 endPoint = (slice + 1) * dims[0] * dims[1]; diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/BadDataNeighborOrientationCheck.cpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/BadDataNeighborOrientationCheck.cpp index e59794da4c..f968875bc5 100644 --- a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/BadDataNeighborOrientationCheck.cpp +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/BadDataNeighborOrientationCheck.cpp @@ -4,8 +4,8 @@ #include "simplnx/DataStructure/DataArray.hpp" #include "simplnx/DataStructure/Geometry/ImageGeom.hpp" #include "simplnx/Utilities/MaskCompareUtilities.hpp" -#include "simplnx/Utilities/MessageHelper.hpp" #include "simplnx/Utilities/NeighborUtilities.hpp" +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" #include @@ -89,8 +89,7 @@ Result<> BadDataNeighborOrientationCheck::operator()() // on the mask array because the algorithm needs to distinguish "newly flipped" from "still bad". std::vector neighborCount(totalPoints, 0); - MessageHelper messageHelper(m_MessageHandler); - ThrottledMessenger throttledMessenger = messageHelper.createThrottledMessenger(); + ThrottledMessageHandler throttledMessenger(m_MessageHandler); // Loop over every point finding the number of neighbors that fall within the // user defined angle tolerance. for(usize voxelIndex = 0; voxelIndex < totalPoints; voxelIndex++) @@ -99,7 +98,7 @@ Result<> BadDataNeighborOrientationCheck::operator()() { return {}; } - throttledMessenger.sendThrottledMessage([&] { return fmt::format("Processing Data {:.2f}% completed", CalculatePercentComplete(voxelIndex, totalPoints)); }); + throttledMessenger.updatePercent("Processing Data", voxelIndex, totalPoints); // If the mask was set to false, then we check this voxel // "Bad" voxels are those whose mask value is false; only these get processed. const bool voxelIsBad = !maskCompare->isTrue(voxelIndex); @@ -186,10 +185,10 @@ Result<> BadDataNeighborOrientationCheck::operator()() { return {}; } - throttledMessenger.sendThrottledMessage([&] { - return fmt::format("Level '{}' of '{}' || Processing Data ('{}') {:.2f}% completed", (startLevel - currentLevel) + 1, startLevel - m_InputValues->NumberOfNeighbors, loopNumber, - CalculatePercentComplete(voxelIndex, totalPoints)); - }); + // The label varies per level and loop, so the text is assembled only when a message is + // actually due rather than on every iteration. + throttledMessenger.queueMessage("Level '{}' of '{}' || Processing Data ('{}') {:.2f}% completed", (startLevel - currentLevel) + 1, startLevel - m_InputValues->NumberOfNeighbors, loopNumber, + CalculatePercentComplete(voxelIndex, totalPoints)); // If the current voxel's neighbor count is >= the current level and the mask is FALSE, // we flip the voxel to TRUE and recompute its (still-bad) neighbors' counts below. diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeAvgCAxes.cpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeAvgCAxes.cpp index ea98d090e4..60481b8916 100644 --- a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeAvgCAxes.cpp +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeAvgCAxes.cpp @@ -69,7 +69,7 @@ Result<> ComputeAvgCAxes::operator()() std::vector cellCount(totalFeatures, 0); - m_MessageHandler({IFilter::Message::Type::Info, "Computing cell contributions"}); + m_MessageHandler.sendInfoMessage("Computing cell contributions"); // Loop over each cell for(usize i = 0; i < totalPoints; i++) @@ -129,7 +129,7 @@ Result<> ComputeAvgCAxes::operator()() } // Now that each feature's Axis is summed up, compute the final average C-Axis - m_MessageHandler({IFilter::Message::Type::Info, "Computing final feature average C-Axis values"}); + m_MessageHandler.sendInfoMessage("Computing final feature average C-Axis values"); for(usize currentFeatureId = 0; currentFeatureId < totalFeatures; currentFeatureId++) { diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeAvgOrientations.cpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeAvgOrientations.cpp index 1a74eb9b68..a4b2b536ee 100644 --- a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeAvgOrientations.cpp +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeAvgOrientations.cpp @@ -2,8 +2,8 @@ #include "simplnx/DataStructure/AttributeMatrix.hpp" #include "simplnx/Utilities/DataArrayUtilities.hpp" -#include "simplnx/Utilities/MessageHelper.hpp" #include "simplnx/Utilities/ParallelDataAlgorithm.hpp" +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" #include #include @@ -219,6 +219,7 @@ ComputeAvgOrientations::ComputeAvgOrientations(DataStructure& dataStructure, con , m_MessageHandler(mesgHandler) , m_ShouldCancel(shouldCancel) , m_InputValues(inputValues) +, m_Throttle(mesgHandler) { } @@ -229,19 +230,7 @@ ComputeAvgOrientations::~ComputeAvgOrientations() noexcept = default; void ComputeAvgOrientations::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_NumberOfFeatures)) * 100.0f); - std::string ss = fmt::format("{}% Complete", progressInt); - m_MessageHandler(IFilter::Message::Type::Info, ss); - - m_InitialPoint = std::chrono::steady_clock::now(); + m_Throttle.incrementPercent(counter, 0); } // ----------------------------------------------------------------------------- @@ -280,13 +269,11 @@ Result<> ComputeAvgOrientations::operator()() return MakeErrorResult(-54670, "A valid Feature level array that stores results was not found."); } - MessageHelper messageHelper(m_MessageHandler); - // Warnings (e.g. dropped voxels/features) from each path are merged and returned together. Result<> finalResult; if(m_InputValues->useRodriguesAverage) { - messageHelper.sendMessage("Computing Rodrigues Average Orientations"); + m_MessageHandler.sendInfoMessage("Computing Rodrigues Average Orientations"); Result<> result = computeRodriguesAverage(); if(result.invalid()) @@ -299,15 +286,15 @@ Result<> ComputeAvgOrientations::operator()() { if(m_InputValues->useVonMisesAverage && !m_InputValues->useWatsonAverage) { - messageHelper.sendMessage("Computing von-Mises Fisher Average Orientations"); + m_MessageHandler.sendInfoMessage("Computing von-Mises Fisher Average Orientations"); } if(!m_InputValues->useVonMisesAverage && m_InputValues->useWatsonAverage) { - messageHelper.sendMessage("Computing Watson Average Orientations"); + m_MessageHandler.sendInfoMessage("Computing Watson Average Orientations"); } if(m_InputValues->useVonMisesAverage && m_InputValues->useWatsonAverage) { - messageHelper.sendMessage("Computing von-Mises Fisher and Watson Average Orientations"); + m_MessageHandler.sendInfoMessage("Computing von-Mises Fisher and Watson Average Orientations"); } Result<> result = computeVmfWatsonAverage(); @@ -407,6 +394,7 @@ Result<> ComputeAvgOrientations::computeVmfWatsonAverage() // default (see vv/ComputeAvgOrientationsFilter.md and ComputeFeatureFaceMisorientation). ParallelDataAlgorithm dataAlg; dataAlg.setParallelizationEnabled(false); + m_Throttle.reset(m_NumberOfFeatures, "Computing Average Orientations"); dataAlg.setRange(0, m_NumberOfFeatures); dataAlg.execute(VmfWatsonSamplingImpl(this, m_InputValues, m_DataStructure, featureNumVoxels, featureIdToPhaseMap)); @@ -455,8 +443,7 @@ Result<> ComputeAvgOrientations::computeRodriguesAverage() // Get the Identity Quaternion static const ebsdlib::QuatF identityQuat(0.0f, 0.0f, 0.0f, 1.0f); - MessageHelper messageHelper(m_MessageHandler); - ThrottledMessenger messenger = messageHelper.createThrottledMessenger(); + ThrottledMessageHandler messenger(m_MessageHandler); for(size_t i = 0; i < totalPoints; i++) { @@ -464,7 +451,7 @@ Result<> ComputeAvgOrientations::computeRodriguesAverage() { return {}; } - messenger.sendThrottledMessage([i, totalPoints]() { return fmt::format("Computing Rodrigues Average: Cell {}/{}", i + 1, totalPoints); }); + messenger.updateCount("Computing Rodrigues Average", i + 1, totalPoints); const int32_t currentFeatureId = featureIds[i]; const int32_t currentPhase = phases[i]; diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeAvgOrientations.hpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeAvgOrientations.hpp index d7845b9ad0..7f117ba72f 100644 --- a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeAvgOrientations.hpp +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeAvgOrientations.hpp @@ -7,6 +7,7 @@ #include "simplnx/DataStructure/DataStructure.hpp" #include "simplnx/DataStructure/IDataArray.hpp" #include "simplnx/Filter/IFilter.hpp" +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" #include @@ -76,10 +77,9 @@ class ORIENTATIONANALYSIS_EXPORT ComputeAvgOrientations Result<> computeVmfWatsonAverage(); // Thread safe Progress Message - std::chrono::steady_clock::time_point m_InitialPoint = std::chrono::steady_clock::now(); mutable std::mutex m_ProgressMessage_Mutex; + ThrottledMessageHandler m_Throttle; size_t m_NumberOfFeatures = 0; - size_t m_ProgressCounter = 0; }; } // namespace nx::core diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeFeatureReferenceCAxisMisorientations.cpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeFeatureReferenceCAxisMisorientations.cpp index 2208e8677f..e6a71a5de3 100644 --- a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeFeatureReferenceCAxisMisorientations.cpp +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeFeatureReferenceCAxisMisorientations.cpp @@ -6,7 +6,7 @@ #include "simplnx/DataStructure/DataArray.hpp" #include "simplnx/DataStructure/Geometry/ImageGeom.hpp" #include "simplnx/Utilities/ImageRotationUtilities.hpp" -#include "simplnx/Utilities/MessageHelper.hpp" +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" #include @@ -163,15 +163,14 @@ Result<> ComputeFeatureReferenceCAxisMisorientations::operator()() // Per-feature average. Explicit NaN when no hex cells contributed (counts == 0); without this // guard, the division below would rely on IEEE 754 0/0 -> NaN, which is correct on every // platform we ship but fragile to FP-environment changes. - MessageHelper messageHelper(m_MessageHandler); - ThrottledMessenger throttledMessenger = messageHelper.createThrottledMessenger(); + ThrottledMessageHandler throttledMessenger(m_MessageHandler); for(usize featureId = 1; featureId < totalFeatures; featureId++) { if(m_ShouldCancel) { return {}; } - throttledMessenger.sendThrottledMessage([&] { return fmt::format("Computing per-feature average {:.2f}% completed", CalculatePercentComplete(featureId, totalFeatures)); }); + throttledMessenger.updatePercent("Computing per-feature average", featureId, totalFeatures); if(counts[featureId] == 0) { diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeGBCD.cpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeGBCD.cpp index 11a1b3a258..3fd721b948 100644 --- a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeGBCD.cpp +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeGBCD.cpp @@ -3,8 +3,8 @@ #include "simplnx/Common/Constants.hpp" #include "simplnx/DataStructure/DataArray.hpp" #include "simplnx/DataStructure/DataGroup.hpp" -#include "simplnx/Utilities/MessageHelper.hpp" #include "simplnx/Utilities/ParallelDataAlgorithm.hpp" +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" #include "simplnx/Utilities/TimeUtilities.hpp" #include @@ -391,13 +391,11 @@ Result<> ComputeGBCD::operator()() SizeGBCD sizeGbcd(triangleChunkSize, k_NumMisoReps, m_InputValues->GBCDRes); int32 totalGBCDBins = sizeGbcd.m_GbcdSizes[0] * sizeGbcd.m_GbcdSizes[1] * sizeGbcd.m_GbcdSizes[2] * sizeGbcd.m_GbcdSizes[3] * sizeGbcd.m_GbcdSizes[4] * 2; - MessageHelper messageHelper(m_MessageHandler); - // create an array to hold the total face area for each phase and initialize the array to 0.0 std::vector totalFaceArea(totalPhases, 0.0); auto startTime = std::chrono::steady_clock::now(); - messageHelper.sendMessage("1/2 Starting GBCD Calculation and Summation Phase"); - ThrottledMessenger throttledMessenger = messageHelper.createThrottledMessenger(); + m_MessageHandler.sendInfoMessage("1/2 Starting GBCD Calculation and Summation Phase"); + ThrottledMessageHandler throttledMessenger(m_MessageHandler); for(usize i = 0; i < totalFaces; i = i + triangleChunkSize) { @@ -452,8 +450,10 @@ Result<> ComputeGBCD::operator()() } } - throttledMessenger.sendThrottledMessage([&]() { - auto currentTime = throttledMessenger.getLastTime(); + // The functor form: the rate calculation reads the clock and advances startTime, so it must run + // only on the iterations that actually send. + throttledMessenger.queueMessage([&]() { + auto currentTime = std::chrono::steady_clock::now(); const usize k_LastTriangleIndex = i + triangleChunkSize; float32 currentRate = static_cast(triangleChunkSize) / static_cast(std::chrono::duration_cast(currentTime - startTime).count()); uint64 estimatedTime = static_cast(totalFaces - k_LastTriangleIndex) / currentRate; @@ -462,7 +462,7 @@ Result<> ComputeGBCD::operator()() }); } - messageHelper.sendMessage("2/2 Starting GBCD Normalization Phase"); + m_MessageHandler.sendInfoMessage("2/2 Starting GBCD Normalization Phase"); for(int32 i = 0; i < totalPhases; i++) { diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeGBCDMetricBased.cpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeGBCDMetricBased.cpp index 890b230f62..1aa9ca3648 100644 --- a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeGBCDMetricBased.cpp +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeGBCDMetricBased.cpp @@ -415,7 +415,7 @@ Result<> ComputeGBCDMetricBased::operator()() } // ------------------------------ generation of sampling points ---------------------------------- - m_MessageHandler(IFilter::Message::Type::Info, "Generating sampling points"); + m_MessageHandler.sendInfoMessage("Generating sampling points"); // generate "Golden Section Spiral", see http://www.softimageblog.com/archives/115 const int32 numSamplePtsWholeSphere = 2 * m_InputValues->NumSamplPts; // here we generate points on the whole sphere @@ -484,8 +484,8 @@ Result<> ComputeGBCDMetricBased::operator()() { return {}; } - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Step 1/2: Selecting Triangles with the Specified Misorientation ({}% completed)", - static_cast(100.0 * static_cast(i) / static_cast(numMeshTriangles)))); + m_MessageHandler.sendInfoMessage( + fmt::format("Step 1/2: Selecting Triangles with the Specified Misorientation ({}% completed)", static_cast(100.0 * static_cast(i) / static_cast(numMeshTriangles)))); if(i + triChunkSize >= numMeshTriangles) { triChunkSize = numMeshTriangles - i; @@ -550,8 +550,8 @@ Result<> ComputeGBCDMetricBased::operator()() { return {}; } - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Step 2/2: Computing Distribution Values at the Section of Interest ({}% completed)", - static_cast(100.0 * static_cast(i) / static_cast(samplePtsX.size())))); + m_MessageHandler.sendInfoMessage(fmt::format("Step 2/2: Computing Distribution Values at the Section of Interest ({}% completed)", + static_cast(100.0 * static_cast(i) / static_cast(samplePtsX.size())))); if(i + pointsChunkSize >= samplePtsX.size()) { pointsChunkSize = samplePtsX.size() - i; diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeGBCDPoleFigure.cpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeGBCDPoleFigure.cpp index 179c3b4028..ea51b4c914 100644 --- a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeGBCDPoleFigure.cpp +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeGBCDPoleFigure.cpp @@ -318,7 +318,7 @@ Result<> ComputeGBCDPoleFigure::operator()() float32 yRes = 2.0f / static_cast(yPoints); float32 zRes = (xRes + yRes) / 2.0F; - m_MessageHandler({IFilter::Message::Type::Info, fmt::format("Generating Intensity Plot for phase {}", m_InputValues->PhaseOfInterest)}); + m_MessageHandler.sendInfoMessage(fmt::format("Generating Intensity Plot for phase {}", m_InputValues->PhaseOfInterest)); typename IParallelAlgorithm::AlgorithmArrays algArrays; algArrays.push_back(&poleFigure); diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeGBPDMetricBased.cpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeGBPDMetricBased.cpp index a236af69d4..a09bac8f10 100644 --- a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeGBPDMetricBased.cpp +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeGBPDMetricBased.cpp @@ -373,7 +373,7 @@ Result<> ComputeGBPDMetricBased::operator()() auto ballVolume = static_cast(nSym) * 2.0 * (1.0 - std::cos(limitDist)); // ------------------------------ generation of sampling points ---------------------------------- - m_MessageHandler(IFilter::Message::Type::Info, "Generating sampling points"); + m_MessageHandler.sendInfoMessage("Generating sampling points"); // generate "Golden Section Spiral", see http://www.softimageblog.com/archives/115 const int numSamplePtsWholeSphere = 2 * m_InputValues->NumSamplPts; // here we generate points on the whole sphere @@ -583,7 +583,7 @@ Result<> ComputeGBPDMetricBased::operator()() { return {}; } - m_MessageHandler(IFilter::Message::Type::Info, "Selecting triangles corresponding to Phase Of Interest"); + m_MessageHandler.sendInfoMessage("Selecting triangles corresponding to Phase Of Interest"); if(i + triChunkSize >= numMeshTriangles) { triChunkSize = numMeshTriangles - i; @@ -647,7 +647,7 @@ Result<> ComputeGBPDMetricBased::operator()() { return {}; } - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Determining GBPD values ({}%)", static_cast(100.0 * static_cast(i) / static_cast(samplePtsX.size())))); + m_MessageHandler.sendInfoMessage(fmt::format("Determining GBPD values ({}%)", static_cast(100.0 * static_cast(i) / static_cast(samplePtsX.size())))); if(i + pointsChunkSize >= samplePtsX.size()) { pointsChunkSize = samplePtsX.size() - i; diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeKernelAvgMisorientations.cpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeKernelAvgMisorientations.cpp index b7170904a0..4d48e96548 100644 --- a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeKernelAvgMisorientations.cpp +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeKernelAvgMisorientations.cpp @@ -4,8 +4,8 @@ #include "simplnx/DataStructure/DataArray.hpp" #include "simplnx/DataStructure/DataGroup.hpp" #include "simplnx/DataStructure/Geometry/ImageGeom.hpp" -#include "simplnx/Utilities/MessageHelper.hpp" #include "simplnx/Utilities/ParallelData3DAlgorithm.hpp" +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" #include @@ -19,9 +19,9 @@ namespace class FindKernelAvgMisorientationsImpl { public: - FindKernelAvgMisorientationsImpl(ProgressMessageHelper& progressMessenger, DataStructure& dataStructure, const ComputeKernelAvgMisorientationsInputValues* inputValues, + FindKernelAvgMisorientationsImpl(ComputeKernelAvgMisorientations* filter, DataStructure& dataStructure, const ComputeKernelAvgMisorientationsInputValues* inputValues, const std::atomic_bool& shouldCancel) - : m_ProgressMessageHelper(progressMessenger) + : m_Filter(filter) , m_DataStructure(dataStructure) , m_InputValues(inputValues) , m_ShouldCancel(shouldCancel) @@ -60,8 +60,6 @@ class FindKernelAvgMisorientationsImpl usize counter = 0; usize increment = std::max(static_cast(1), (zEnd - zStart) / 100); - ProgressMessenger progressMessenger = m_ProgressMessageHelper.createProgressMessenger(); - auto xPoints = static_cast(udims[0]); auto yPoints = static_cast(udims[1]); auto zPoints = static_cast(udims[2]); @@ -69,7 +67,7 @@ class FindKernelAvgMisorientationsImpl { if(counter > increment) { - progressMessenger.sendProgressMessage(counter); + m_Filter->sendThreadSafeProgressMessage(counter); counter = 0; } @@ -150,7 +148,7 @@ class FindKernelAvgMisorientationsImpl } } } - progressMessenger.sendProgressMessage(counter); + m_Filter->sendThreadSafeProgressMessage(counter); } void operator()(const Range3D& range) const @@ -159,7 +157,7 @@ class FindKernelAvgMisorientationsImpl } private: - ProgressMessageHelper& m_ProgressMessageHelper; + ComputeKernelAvgMisorientations* m_Filter = nullptr; DataStructure& m_DataStructure; const ComputeKernelAvgMisorientationsInputValues* m_InputValues = nullptr; const std::atomic_bool& m_ShouldCancel; @@ -175,6 +173,7 @@ ComputeKernelAvgMisorientations::ComputeKernelAvgMisorientations(DataStructure& , m_InputValues(inputValues) , m_ShouldCancel(shouldCancel) , m_MessageHandler(mesgHandler) +, m_Throttle(mesgHandler) { } @@ -182,16 +181,19 @@ ComputeKernelAvgMisorientations::ComputeKernelAvgMisorientations(DataStructure& ComputeKernelAvgMisorientations::~ComputeKernelAvgMisorientations() noexcept = default; // ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- +void ComputeKernelAvgMisorientations::sendThreadSafeProgressMessage(usize counter) +{ + std::lock_guard guard(m_ProgressMessage_Mutex); + m_Throttle.incrementPercent(counter); +} + Result<> ComputeKernelAvgMisorientations::operator()() { const auto& imageGeom = m_DataStructure.getDataRefAs(m_InputValues->InputImageGeometry); SizeVec3 udims = imageGeom.getDimensions(); - MessageHelper messageHelper(m_MessageHandler); - ProgressMessageHelper progressMessageHelper = messageHelper.createProgressMessageHelper(); - - progressMessageHelper.setMaxProgresss(udims[2] * udims[1] * udims[0]); - progressMessageHelper.setProgressMessageTemplate("Finding Kernel Average Misorientations || {:.2f}%"); + m_Throttle.reset(udims[2] * udims[1] * udims[0], "Finding Kernel Average Misorientations"); typename IParallelAlgorithm::AlgorithmArrays algArrays; algArrays.push_back(m_DataStructure.getDataAs(m_InputValues->CellPhasesArrayPath)); @@ -203,7 +205,7 @@ Result<> ComputeKernelAvgMisorientations::operator()() ParallelData3DAlgorithm parallelAlgorithm; parallelAlgorithm.setRange(Range3D(0, udims[0], 0, udims[1], 0, udims[2])); parallelAlgorithm.requireArraysInMemory(algArrays); - parallelAlgorithm.execute(FindKernelAvgMisorientationsImpl(progressMessageHelper, m_DataStructure, m_InputValues, m_ShouldCancel)); + parallelAlgorithm.execute(FindKernelAvgMisorientationsImpl(this, m_DataStructure, m_InputValues, m_ShouldCancel)); return {}; } diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeKernelAvgMisorientations.hpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeKernelAvgMisorientations.hpp index 49329656be..015c24d880 100644 --- a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeKernelAvgMisorientations.hpp +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeKernelAvgMisorientations.hpp @@ -5,7 +5,10 @@ #include "simplnx/DataStructure/DataPath.hpp" #include "simplnx/DataStructure/DataStructure.hpp" #include "simplnx/Filter/IFilter.hpp" +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" + #include "simplnx/Parameters/VectorParameter.hpp" +#include #include @@ -46,11 +49,19 @@ class ORIENTATIONANALYSIS_EXPORT ComputeKernelAvgMisorientations Result<> operator()(); + /** + * @brief Thread-safe progress update. Safe to call from ParallelDataAlgorithm workers. + * @param counter Items completed since the previous call + */ + void sendThreadSafeProgressMessage(usize counter); + private: DataStructure& m_DataStructure; const ComputeKernelAvgMisorientationsInputValues* m_InputValues = nullptr; const std::atomic_bool& m_ShouldCancel; const IFilter::MessageHandler& m_MessageHandler; + mutable std::mutex m_ProgressMessage_Mutex; + ThrottledMessageHandler m_Throttle; }; } // namespace nx::core diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeShapesTriangleGeom.cpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeShapesTriangleGeom.cpp index 3ff7e61f9f..97b7a3e423 100644 --- a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeShapesTriangleGeom.cpp +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ComputeShapesTriangleGeom.cpp @@ -484,7 +484,7 @@ void ComputeShapesTriangleGeom::updateResults(const std::vector ConvertHexGridToSquareGrid::operator()() ::Converter converter(getCancel(), m_InputValues->OutputPath, m_InputValues->OutputFilePrefix, m_InputValues->XYSpacing); for(const auto& filepath : fileList) { - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Now Processing: {}", filepath)); + m_MessageHandler.sendInfoMessage(fmt::format("Now Processing: {}", filepath)); result = MergeResults(converter(filepath), result); if(result.invalid()) @@ -408,7 +408,7 @@ Result<> ConvertHexGridToSquareGrid::operator()() progress = static_cast(z - m_InputValues->InputFileListInfo.startIndex - 1); progress = static_cast(100.0f * static_cast(progress) / total); std::string msg = "Converted File: " + filepath; - m_MessageHandler(IFilter::Message::Type::Progress, msg, progress); + m_MessageHandler.sendProgressMessage(msg, progress); } if(getCancel()) diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ConvertOrientations.cpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ConvertOrientations.cpp index 84bf5f8b3a..741ec68033 100644 --- a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ConvertOrientations.cpp +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ConvertOrientations.cpp @@ -85,6 +85,7 @@ ConvertOrientations::ConvertOrientations(DataStructure& dataStructure, const IFi , m_InputValues(inputValues) , m_ShouldCancel(shouldCancel) , m_MessageHandler(mesgHandler) +, m_Throttle(mesgHandler) { } @@ -101,17 +102,7 @@ bool ConvertOrientations::shouldCancel() const void ConvertOrientations::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_TotalPoints)) * 100.0f); - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Converting Orientations: {}% Complete", progressInt)); - m_InitialPoint = std::chrono::steady_clock::now(); + m_Throttle.incrementPercent(counter, 0); } // ----------------------------------------------------------------------------- @@ -121,10 +112,10 @@ Result<> ConvertOrientations::operator()() auto& inputArray = m_DataStructure.getDataRefAs(m_InputValues->InputOrientationArrayPath); auto& outputArray = m_DataStructure.getDataRefAs(outputDataPath); size_t totalPoints = inputArray.getNumberOfTuples(); - m_TotalPoints = totalPoints; + m_Throttle.reset(totalPoints, "Converting Orientations"); - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Converting {} orientations from {} to {}", totalPoints, k_TypeNames[static_cast(m_InputValues->InputType)], - k_TypeNames[static_cast(m_InputValues->OutputType)])); + m_MessageHandler.sendInfoMessage( + fmt::format("Converting {} orientations from {} to {}", totalPoints, k_TypeNames[static_cast(m_InputValues->InputType)], k_TypeNames[static_cast(m_InputValues->OutputType)])); // Allow data-based parallelization; require both arrays resident in memory for out-of-core stores. ParallelDataAlgorithm parallelAlgorithm; diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ConvertOrientations.hpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ConvertOrientations.hpp index 4d1a408520..44fc01b2d7 100644 --- a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ConvertOrientations.hpp +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ConvertOrientations.hpp @@ -8,6 +8,7 @@ #include "simplnx/Parameters/ArraySelectionParameter.hpp" #include "simplnx/Parameters/ChoicesParameter.hpp" #include "simplnx/Parameters/DataObjectNameParameter.hpp" +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" #include @@ -72,10 +73,8 @@ class ORIENTATIONANALYSIS_EXPORT ConvertOrientations 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_TotalPoints = 0; - size_t m_ProgressCounter = 0; + ThrottledMessageHandler m_Throttle; }; } // namespace nx::core diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/EbsdToH5Ebsd.cpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/EbsdToH5Ebsd.cpp index f7d77ab956..2c643c0ee8 100644 --- a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/EbsdToH5Ebsd.cpp +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/EbsdToH5Ebsd.cpp @@ -236,7 +236,7 @@ Result<> EbsdToH5Ebsd::operator()() int32_t totalSlicesImported = 0; for(const auto& ebsdFName : fileList) { - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Converting File: '{}'", ebsdFName)); + m_MessageHandler.sendInfoMessage(fmt::format("Converting File: '{}'", ebsdFName)); err = fileImporter->importFile(fileId, z, ebsdFName); if(err < 0) diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/GroupMicroTextureRegions.cpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/GroupMicroTextureRegions.cpp index c5061b6155..44c572a1ed 100644 --- a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/GroupMicroTextureRegions.cpp +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/GroupMicroTextureRegions.cpp @@ -4,7 +4,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/LaueOps/LaueOps.h" @@ -79,8 +79,7 @@ void GroupMicroTextureRegions::randomizeParentIds(usize totalPoints, usize total // ----------------------------------------------------------------------------- Result<> GroupMicroTextureRegions::execute() { - MessageHelper messageHelper(m_MessageHandler); - ThrottledMessenger throttledMessenger = messageHelper.createThrottledMessenger(); + ThrottledMessageHandler throttledMessenger(m_MessageHandler); NeighborList& featureNeighborListRef = m_DataStructure.getDataRefAs>(m_InputValues->ContiguousNeighborListArrayPath); NeighborList* nonContigNeighListPtr = nullptr; @@ -146,7 +145,7 @@ Result<> GroupMicroTextureRegions::execute() } } - throttledMessenger.sendThrottledMessage([&]() { return fmt::format("Parent Count: {}", parentCount); }); + throttledMessenger.queueMessage("Parent Count: {}", parentCount); } return {}; } @@ -154,7 +153,6 @@ Result<> GroupMicroTextureRegions::execute() // ----------------------------------------------------------------------------- Result<> GroupMicroTextureRegions::operator()() { - MessageHelper messageHelper(m_MessageHandler); m_Generator = std::mt19937_64(m_InputValues->SeedValue); m_Distribution = std::uniform_real_distribution(0.0f, 1.0f); @@ -166,7 +164,7 @@ Result<> GroupMicroTextureRegions::operator()() m_FeatureParentIds.fill(-1); // Execute the main grouping algorithm - messageHelper.sendMessage(fmt::format("Start Grouping.....")); + m_MessageHandler.sendInfoMessage(fmt::format("Start Grouping.....")); // Execute the grouping algorithm Result<> result = execute(); @@ -192,7 +190,7 @@ Result<> GroupMicroTextureRegions::operator()() if(m_InputValues->RandomizeParentIds) { - messageHelper.sendMessage(fmt::format("Randomizing Parent Ids")); + m_MessageHandler.sendInfoMessage(fmt::format("Randomizing Parent Ids")); randomizeParentIds(totalPoints, m_NumTuples); } diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/MergeTwins.cpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/MergeTwins.cpp index 2700ac0b82..aa3c5de76e 100644 --- a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/MergeTwins.cpp +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/MergeTwins.cpp @@ -241,7 +241,7 @@ Result<> MergeTwins::operator()() // would look like a smooth gradient. This is a user input parameter if(m_InputValues->RandomizeParentIds) { // Randomize Parent IDs - m_MessageHandler({IFilter::Message::Type::Info, "Randomizing Parent Ids...."}); + m_MessageHandler.sendInfoMessage("Randomizing Parent Ids...."); ClusterUtilities::RandomizeFeatureIds(featureParentIds, numParents); } diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/NeighborOrientationCorrelation.cpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/NeighborOrientationCorrelation.cpp index 6c3c482d86..ffe738f34f 100644 --- a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/NeighborOrientationCorrelation.cpp +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/NeighborOrientationCorrelation.cpp @@ -9,9 +9,9 @@ #include "simplnx/DataStructure/IDataArray.hpp" #include "simplnx/DataStructure/INeighborList.hpp" #include "simplnx/DataStructure/StringArray.hpp" -#include "simplnx/Utilities/MessageHelper.hpp" #include "simplnx/Utilities/NeighborUtilities.hpp" #include "simplnx/Utilities/ParallelTaskAlgorithm.hpp" +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" #include @@ -80,9 +80,9 @@ class NeighborOrientationCorrelationTransferDataImpl NeighborOrientationCorrelationTransferDataImpl() = delete; NeighborOrientationCorrelationTransferDataImpl(const NeighborOrientationCorrelationTransferDataImpl&) = default; - NeighborOrientationCorrelationTransferDataImpl(MessageHelper& messageHelper, size_t totalPoints, const std::vector& bestNeighbor, std::shared_ptr arrayPtr, + NeighborOrientationCorrelationTransferDataImpl(NeighborOrientationCorrelation* filterAlg, size_t totalPoints, const std::vector& bestNeighbor, std::shared_ptr arrayPtr, const std::atomic_bool& shouldCancel) - : m_MessageHelper(messageHelper) + : m_FilterAlg(filterAlg) , m_TotalPoints(totalPoints) , m_BestNeighbor(bestNeighbor) , m_ArrayPtr(arrayPtr) @@ -97,7 +97,6 @@ class NeighborOrientationCorrelationTransferDataImpl void operator()() const { - ThrottledMessenger throttledMessenger = m_MessageHelper.createThrottledMessenger(); std::string arrayName = m_ArrayPtr->getName(); for(size_t i = 0; i < m_TotalPoints; i++) { @@ -105,7 +104,7 @@ class NeighborOrientationCorrelationTransferDataImpl { return; } - throttledMessenger.sendThrottledMessage([&]() { return fmt::format("Processing {}: {:.2f}% completed", arrayName, CalculatePercentComplete(i, m_TotalPoints)); }); + m_FilterAlg->sendThreadSafeProgressMessage(fmt::format("Processing {}: {:.2f}% completed", arrayName, CalculatePercentComplete(i, m_TotalPoints))); int64 neighbor = m_BestNeighbor[i]; if(neighbor != -1) { @@ -115,7 +114,7 @@ class NeighborOrientationCorrelationTransferDataImpl } private: - MessageHelper& m_MessageHelper; + NeighborOrientationCorrelation* m_FilterAlg = nullptr; size_t m_TotalPoints = 0; // Reference, not a copy: one task exists per transferred array and bestNeighbor is // 8 bytes per voxel. All tasks finish before the referenced vector leaves scope @@ -132,12 +131,20 @@ NeighborOrientationCorrelation::NeighborOrientationCorrelation(DataStructure& da , m_InputValues(inputValues) , m_ShouldCancel(shouldCancel) , m_MessageHandler(mesgHandler) +, m_Throttle(mesgHandler) { } // ----------------------------------------------------------------------------- NeighborOrientationCorrelation::~NeighborOrientationCorrelation() noexcept = default; +// ----------------------------------------------------------------------------- +void NeighborOrientationCorrelation::sendThreadSafeProgressMessage(const std::string& message) +{ + std::lock_guard guard(m_ProgressMessage_Mutex); + m_Throttle.trySendMessage(message); +} + // ----------------------------------------------------------------------------- Result<> NeighborOrientationCorrelation::operator()() { @@ -168,18 +175,15 @@ Result<> NeighborOrientationCorrelation::operator()() std::vector bestNeighbor(totalPoints, -1); const int32 startLevel = 6; - MessageHelper messageHelper(m_MessageHandler); - - ThrottledMessenger throttledMessenger = messageHelper.createThrottledMessenger(); + ThrottledMessageHandler throttledMessenger(m_MessageHandler); for(int32 currentLevel = startLevel; currentLevel > m_InputValues->Level; currentLevel--) { for(int64 voxelIndex = 0; voxelIndex < static_cast(totalPoints); voxelIndex++) { - throttledMessenger.sendThrottledMessage([&]() { - return fmt::format("Level '{}' of '{}' || Processing Data {:.2f}% completed", (startLevel - currentLevel) + 1, startLevel - m_InputValues->Level, - CalculatePercentComplete(voxelIndex, totalPoints)); - }); + // The label varies per level, so the text is assembled only when a message is due. + throttledMessenger.queueMessage("Level '{}' of '{}' || Processing Data {:.2f}% completed", (startLevel - currentLevel) + 1, startLevel - m_InputValues->Level, + CalculatePercentComplete(voxelIndex, totalPoints)); if(m_ShouldCancel) { @@ -270,7 +274,7 @@ Result<> NeighborOrientationCorrelation::operator()() ParallelTaskAlgorithm parallelTask; for(const auto& arrayPtr : voxelArrays) { - parallelTask.execute(NeighborOrientationCorrelationTransferDataImpl(messageHelper, totalPoints, bestNeighbor, arrayPtr, m_ShouldCancel)); + parallelTask.execute(NeighborOrientationCorrelationTransferDataImpl(this, totalPoints, bestNeighbor, arrayPtr, m_ShouldCancel)); } } diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/NeighborOrientationCorrelation.hpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/NeighborOrientationCorrelation.hpp index 0b21553a6d..9f304f6143 100644 --- a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/NeighborOrientationCorrelation.hpp +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/NeighborOrientationCorrelation.hpp @@ -5,7 +5,10 @@ #include "simplnx/DataStructure/DataPath.hpp" #include "simplnx/DataStructure/DataStructure.hpp" #include "simplnx/Filter/IFilter.hpp" +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" + #include "simplnx/Parameters/MultiArraySelectionParameter.hpp" +#include namespace nx::core { @@ -40,11 +43,19 @@ class ORIENTATIONANALYSIS_EXPORT NeighborOrientationCorrelation Result<> operator()(); + /** + * @brief Thread-safe progress update. Safe to call from the parallel per-array workers. + * @param message Fully rendered progress text for one array + */ + void sendThreadSafeProgressMessage(const std::string& message); + private: DataStructure& m_DataStructure; const NeighborOrientationCorrelationInputValues* m_InputValues = nullptr; const std::atomic_bool& m_ShouldCancel; const IFilter::MessageHandler& m_MessageHandler; + mutable std::mutex m_ProgressMessage_Mutex; + ThrottledMessageHandler m_Throttle; }; } // namespace nx::core diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ReadAngData.cpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ReadAngData.cpp index 7f5b6860c3..2a7245d395 100644 --- a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ReadAngData.cpp +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ReadAngData.cpp @@ -27,7 +27,7 @@ ReadAngData::~ReadAngData() noexcept = default; // ----------------------------------------------------------------------------- Result<> ReadAngData::operator()() { - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Reading .ang file '{}'", m_InputValues->InputFile.string())); + m_MessageHandler.sendInfoMessage(fmt::format("Reading .ang file '{}'", m_InputValues->InputFile.string())); ebsdlib::AngReader reader; reader.setFileName(m_InputValues->InputFile.string()); const int32_t err = reader.readFile(); @@ -45,7 +45,7 @@ Result<> ReadAngData::operator()() return {}; } - m_MessageHandler(IFilter::Message::Type::Info, "Copying cell data into the Image Geometry"); + m_MessageHandler.sendInfoMessage("Copying cell data into the Image Geometry"); return copyRawEbsdData(&reader); } diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ReadCtfData.cpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ReadCtfData.cpp index 208d3b68e5..5b61f27bb6 100644 --- a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ReadCtfData.cpp +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ReadCtfData.cpp @@ -32,7 +32,7 @@ ReadCtfData::~ReadCtfData() noexcept = default; // ----------------------------------------------------------------------------- Result<> ReadCtfData::operator()() { - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Reading .ctf file '{}'", m_InputValues->InputFile.string())); + m_MessageHandler.sendInfoMessage(fmt::format("Reading .ctf file '{}'", m_InputValues->InputFile.string())); ebsdlib::CtfReader reader; reader.setFileName(m_InputValues->InputFile.string()); const int32_t err = reader.readFile(); @@ -55,7 +55,7 @@ Result<> ReadCtfData::operator()() return {}; } - m_MessageHandler(IFilter::Message::Type::Info, "Copying cell data into the Image Geometry"); + m_MessageHandler.sendInfoMessage("Copying cell data into the Image Geometry"); return copyRawEbsdData(&reader); } diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ReadH5Ebsd.cpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ReadH5Ebsd.cpp index 05085246ad..dd779a96f1 100644 --- a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ReadH5Ebsd.cpp +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/ReadH5Ebsd.cpp @@ -169,7 +169,7 @@ nx::core::Result<> LoadEbsdData(const nx::core::ReadH5EbsdInputValues* mInputVal } // Initialize all the arrays with some default values - mMessageHandler(nx::core::IFilter::Message{nx::core::IFilter::Message::Type::Info, fmt::format("Reading EBSD Data from file {}", mInputValues->inputFilePath)}); + mMessageHandler.sendInfoMessage(fmt::format("Reading EBSD Data from file {}", mInputValues->inputFilePath)); uint32_t mRefFrameZDir = ebsdReader->getStackingOrder(); ebsdReader->setSliceStart(mInputValues->startSlice); @@ -389,7 +389,7 @@ Result<> ReadH5Ebsd::operator()() args.insertOrAssign(::k_RotateSliceBySlice_Key, std::make_any(true)); // Preflight the filter and check result - m_MessageHandler(nx::core::IFilter::Message{IFilter::Message::Type::Info, fmt::format("Preflighting {}...", filter->humanName())}); + m_MessageHandler.sendInfoMessage(fmt::format("Preflighting {}...", filter->humanName())); nx::core::IFilter::PreflightResult preflightResult = filter->preflight(m_DataStructure, args); if(preflightResult.outputActions.invalid()) { @@ -402,7 +402,7 @@ Result<> ReadH5Ebsd::operator()() } // Execute the filter and check the result - m_MessageHandler(nx::core::IFilter::Message{IFilter::Message::Type::Info, fmt::format("Executing {}", filter->humanName())}); + m_MessageHandler.sendInfoMessage(fmt::format("Executing {}", filter->humanName())); auto executeResult = filter->execute(m_DataStructure, args, nullptr, m_MessageHandler, m_ShouldCancel); if(executeResult.result.invalid()) { diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/RotateEulerRefFrame.cpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/RotateEulerRefFrame.cpp index 943fb2a2ea..339d608792 100644 --- a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/RotateEulerRefFrame.cpp +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/RotateEulerRefFrame.cpp @@ -5,8 +5,8 @@ #include "simplnx/Common/Array.hpp" #include "simplnx/Common/Numbers.hpp" #include "simplnx/DataStructure/DataArray.hpp" -#include "simplnx/Utilities/MessageHelper.hpp" #include "simplnx/Utilities/ParallelDataAlgorithm.hpp" +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" #include #include @@ -28,12 +28,12 @@ class RotateEulerRefFrameImpl { public: - RotateEulerRefFrameImpl(Float32Array& data, const FloatVec3& rotAxis, float angle, const std::atomic_bool& shouldCancel, ProgressMessageHelper& progressMessageHelper) + RotateEulerRefFrameImpl(RotateEulerRefFrame* filter, Float32Array& data, const FloatVec3& rotAxis, float angle, const std::atomic_bool& shouldCancel) : m_CellEulerAngles(data) , m_RotationAxis(rotAxis) , m_Angle(angle) , m_ShouldCancel(shouldCancel) - , m_ProgressMessageHelper(progressMessageHelper) + , m_Filter(filter) { } ~RotateEulerRefFrameImpl() = default; @@ -47,8 +47,6 @@ class RotateEulerRefFrameImpl OrientationUtilities::Matrix3dR rotMat = om.toEigenGMatrix(); - ProgressMessenger progressMessenger = m_ProgressMessageHelper.createProgressMessenger(); - usize counter = 0; usize counterIncrement = (end - start) / 100; for(size_t i = start; i < end; i++) @@ -59,7 +57,7 @@ class RotateEulerRefFrameImpl } if(counter >= counterIncrement) { - progressMessenger.sendProgressMessage(counter); + m_Filter->sendThreadSafeProgressMessage(counter); counter = 0; } @@ -72,7 +70,7 @@ class RotateEulerRefFrameImpl m_CellEulerAngles[3 * i + 2] = eu[2]; counter++; } - progressMessenger.sendProgressMessage(counter); + m_Filter->sendThreadSafeProgressMessage(counter); } void operator()(const Range& range) const @@ -85,7 +83,7 @@ class RotateEulerRefFrameImpl FloatVec3 m_RotationAxis; float m_Angle = 0.0F; const std::atomic_bool& m_ShouldCancel; - ProgressMessageHelper& m_ProgressMessageHelper; + RotateEulerRefFrame* m_Filter = nullptr; }; } // namespace @@ -93,6 +91,7 @@ class RotateEulerRefFrameImpl RotateEulerRefFrame::RotateEulerRefFrame(DataStructure& dataStructure, const IFilter::MessageHandler& mesgHandler, const std::atomic_bool& shouldCancel, RotateEulerRefFrameInputValues* inputValues) : m_DataStructure(dataStructure) , m_MessageHandler(mesgHandler) +, m_Throttle(mesgHandler) , m_ShouldCancel(shouldCancel) , m_InputValues(inputValues) { @@ -102,6 +101,13 @@ RotateEulerRefFrame::RotateEulerRefFrame(DataStructure& dataStructure, const IFi RotateEulerRefFrame::~RotateEulerRefFrame() noexcept = default; // ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- +void RotateEulerRefFrame::sendThreadSafeProgressMessage(usize counter) +{ + std::lock_guard guard(m_ProgressMessage_Mutex); + m_Throttle.incrementPercent(counter); +} + Result<> RotateEulerRefFrame::operator()() { if(m_ShouldCancel) @@ -123,10 +129,7 @@ Result<> RotateEulerRefFrame::operator()() } axis = axis.normalize(); - MessageHelper messageHelper(m_MessageHandler); - ProgressMessageHelper progressMessageHelper = messageHelper.createProgressMessageHelper(); - progressMessageHelper.setMaxProgresss(totalElements); - progressMessageHelper.setProgressMessageTemplate("RotateEulerRefFrame: {:.2f}% complete"); + m_Throttle.reset(totalElements, "RotateEulerRefFrame"); // Data-based parallelization: each worker reads and writes only its own disjoint tuple range of the // in-place Euler array. Per the project thread-safety policy, concurrent DataStore access is unsafe for @@ -137,7 +140,7 @@ Result<> RotateEulerRefFrame::operator()() IParallelAlgorithm::AlgorithmArrays algArrays; algArrays.push_back(&eulerAngles); dataAlg.requireArraysInMemory(algArrays); - dataAlg.execute(RotateEulerRefFrameImpl(eulerAngles, axis, m_InputValues->rotationAxis[3], m_ShouldCancel, progressMessageHelper)); + dataAlg.execute(RotateEulerRefFrameImpl(this, eulerAngles, axis, m_InputValues->rotationAxis[3], m_ShouldCancel)); return {}; } diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/RotateEulerRefFrame.hpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/RotateEulerRefFrame.hpp index 7becf79951..9332492fc7 100644 --- a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/RotateEulerRefFrame.hpp +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/RotateEulerRefFrame.hpp @@ -4,6 +4,9 @@ #include "simplnx/DataStructure/DataStructure.hpp" #include "simplnx/Filter/IFilter.hpp" +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" + +#include namespace nx::core { @@ -34,9 +37,17 @@ class ORIENTATIONANALYSIS_EXPORT RotateEulerRefFrame Result<> operator()(); bool shouldCancel() const; + /** + * @brief Thread-safe progress update. Safe to call from ParallelDataAlgorithm workers. + * @param counter Items completed since the previous call + */ + void sendThreadSafeProgressMessage(usize counter); + private: DataStructure& m_DataStructure; const IFilter::MessageHandler& m_MessageHandler; + mutable std::mutex m_ProgressMessage_Mutex; + ThrottledMessageHandler m_Throttle; const std::atomic_bool& m_ShouldCancel; const RotateEulerRefFrameInputValues* m_InputValues = nullptr; }; diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/WritePoleFigure.cpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/WritePoleFigure.cpp index d63ac8e41b..032b0dc939 100644 --- a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/WritePoleFigure.cpp +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/WritePoleFigure.cpp @@ -609,7 +609,7 @@ Result<> WritePoleFigure::operator()() config.flipFinalImage = m_InputValues->FlipFinalImage; config.axisNames = std::vector{"A1", "A2", "A3"}; - m_MessageHandler({IFilter::Message::Type::Info, fmt::format("Generating Pole Figures for Phase {}", phase)}); + m_MessageHandler.sendInfoMessage(fmt::format("Generating Pole Figures for Phase {}", phase)); if(m_InputValues->SaveIntensityData) { std::vector intensityImages; @@ -650,8 +650,8 @@ Result<> WritePoleFigure::operator()() intensityImages = createIntensityPoleFigures(config, m_InputValues->NormalizeToMRD); break; default: - m_MessageHandler({IFilter::Message::Type::Warning, - fmt::format("Phase {} has unknown crystal structure value {}; no pole figures will be generated for this phase.", phase, static_cast(crystalStructures[phase]))}); + m_MessageHandler.sendWarningMessage( + fmt::format("Phase {} has unknown crystal structure value {}; no pole figures will be generated for this phase.", phase, static_cast(crystalStructures[phase]))); break; } diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/WriteStatsGenOdfAngleFile.cpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/WriteStatsGenOdfAngleFile.cpp index 021f537479..655cc80896 100644 --- a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/WriteStatsGenOdfAngleFile.cpp +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/Algorithms/WriteStatsGenOdfAngleFile.cpp @@ -77,7 +77,7 @@ Result<> WriteStatsGenOdfAngleFile::operator()() continue; } - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Writing file for phase '{}'", phase)); + m_MessageHandler.sendInfoMessage(fmt::format("Writing file for phase '{}'", phase)); const std::string absFilePath = fmt::format("{}/{}_Phase_{}{}", absPath, fName, phase, suffix); diff --git a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/utilities/IEbsdOemReader.hpp b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/utilities/IEbsdOemReader.hpp index ee23773c50..8ce43d7f03 100644 --- a/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/utilities/IEbsdOemReader.hpp +++ b/src/Plugins/OrientationAnalysis/src/OrientationAnalysis/utilities/IEbsdOemReader.hpp @@ -60,7 +60,7 @@ class ORIENTATIONANALYSIS_EXPORT IEbsdOemReader int index = 0; for(const auto& currentScanName : m_InputValues->SelectedScanNames.scanNames) { - m_MessageHandler({IFilter::Message::Type::Info, fmt::format("Importing Index {}", currentScanName)}); + m_MessageHandler.sendInfoMessage(fmt::format("Importing Index {}", currentScanName)); Result<> readResults = readData(currentScanName); if(readResults.invalid()) diff --git a/src/Plugins/OrientationAnalysis/test/ConvertOrientationsTest.cpp b/src/Plugins/OrientationAnalysis/test/ConvertOrientationsTest.cpp index 1752dceeb5..c7be6eefc5 100644 --- a/src/Plugins/OrientationAnalysis/test/ConvertOrientationsTest.cpp +++ b/src/Plugins/OrientationAnalysis/test/ConvertOrientationsTest.cpp @@ -111,7 +111,7 @@ void _make_code() std::cout << "else if( inputType == ebsdlib::orientations::Type::" << names[i] << " && outputType == ebsdlib::orientations::Type::" << names[o] << ")\n" << "{\n"; - std::cout << " messageHandler(nx::core::IFilter::Message{nx::core::IFilter::Message::Type::Info, \"Converting " << names[i] << " to " << names[o] << "\"});\n"; + std::cout << " messageHandler.sendInfoMessage(\"Converting " << names[i] << " to " << names[o] << "\");\n"; if(inRep[i] == "qu") { diff --git a/src/Plugins/OrientationAnalysis/vv/BadDataNeighborOrientationCheckFilter.md b/src/Plugins/OrientationAnalysis/vv/BadDataNeighborOrientationCheckFilter.md index db218dcfa5..a0909a50fb 100644 --- a/src/Plugins/OrientationAnalysis/vv/BadDataNeighborOrientationCheckFilter.md +++ b/src/Plugins/OrientationAnalysis/vv/BadDataNeighborOrientationCheckFilter.md @@ -38,7 +38,7 @@ 1. **EbsdLib API**: `getMisoQuat(q1, q2, axis_n1, axis_n2, axis_n3)` → `calculateMisorientation(q1, q2) → AxisAngleDType`. Modernized return type; mathematically equivalent in the absence of precision-sensitive boundary cases. 2. **Mask handling**: legacy unpacks `BoolArrayType` directly; SIMPLNX uses `MaskCompareUtilities::MaskCompare` to handle both `Bool` and `UInt8` mask backings transparently. UX-only, no behavioral delta. 3. **Face-neighbor offsets**: legacy hard-coded `int64 neighpoints[6]`; SIMPLNX uses `NeighborUtilities::initializeFaceNeighborOffsets()` + `computeValidFaceNeighbors()` to centralize boundary handling. PR #1590 made this 2D-aware (correctly skips +/-Z neighbors when `dims[2] == 1`). -4. **Progress reporting**: legacy direct `notifyStatusMessage`; SIMPLNX uses `ThrottledMessenger` + `MessageHelper` with stage info (`Level X of Y`). UX-only. +4. **Progress reporting**: legacy direct `notifyStatusMessage`; SIMPLNX emits throttled progress feedback with stage info (`Level X of Y`). UX-only. 5. **`quat.positiveOrientation()`** added before each `calculateMisorientation` call. Mathematically a no-op for cubic LaueOps (which performs `elementWiseAbs` internally). No behavioral delta. 6. **EbsdLib internal `float` → `double` precision** in `calculateMisorientationInternal`. Modern API takes `QuatD`; legacy was `QuatF`. Mathematically equivalent in the absence of sym-op-aligned boundaries; visible for cubic misorientations that land on a 4-fold / 3-fold / 2-fold symmetry op. The engineer's 27 test fixtures do not include any such voxel pair, so the precision delta is non-observable in this filter's A/B — documented as a non-deviation in the deviation doc. 7. **Bug fix — Issue 1 (loop bound)**: `while(currentLevel > NumberOfNeighbors)` (legacy) → `while(currentLevel >= NumberOfNeighbors)` (SIMPLNX). Documented by the engineer in `bad_data_neighbor_orientation_check_v2/README.md` §"Issue 1". Confirmed by direct A/B against legacy 6.5.171. Tracked as **Deviation D1**. diff --git a/src/Plugins/OrientationAnalysis/vv/ComputeAvgOrientationsFilter.md b/src/Plugins/OrientationAnalysis/vv/ComputeAvgOrientationsFilter.md index 2c4b1e730c..97301eb85c 100644 --- a/src/Plugins/OrientationAnalysis/vv/ComputeAvgOrientationsFilter.md +++ b/src/Plugins/OrientationAnalysis/vv/ComputeAvgOrientationsFilter.md @@ -48,7 +48,7 @@ Line-by-line review performed via the `review-algorithm` skill on the already-or - **Thread safety:** `computeVmfWatsonAverage` now calls `dataAlg.setParallelizationEnabled(false)`. The worker read/wrote shared `DataArray`/`DataStore` objects concurrently, which the simplnx thread-safety policy forbids even at distinct indices. Serial execution matches the `ComputeFeatureFaceMisorientation` precedent. - **Cancel checking:** added `m_Filter->getCancel()` (new accessor) to the vMF/Watson per-feature loop, which previously could not be cancelled during EM. -- **Progress messaging:** `computeRodriguesAverage` now emits throttled progress via `MessageHelper`/`ThrottledMessenger` over the per-cell loop. +- **Progress messaging:** `computeRodriguesAverage` now emits throttled progress feedback over the per-cell loop. - **Robustness:** guarded `ops[laueClass]` / `orientationOps[xtal]` against out-of-range `CrystalStructures` values (e.g. `999`/Unknown) to avoid undefined behavior; the feature/voxel is skipped (vMF/Watson outputs stay NaN). - **Cosmetic:** removed commented-out dead code, fixed the `dataStruture` parameter typo, removed the unused `m_LastProgressInt` member. diff --git a/src/Plugins/OrientationAnalysis/vv/RotateEulerRefFrameFilter.md b/src/Plugins/OrientationAnalysis/vv/RotateEulerRefFrameFilter.md index 33384e35fd..1a91caa10f 100644 --- a/src/Plugins/OrientationAnalysis/vv/RotateEulerRefFrameFilter.md +++ b/src/Plugins/OrientationAnalysis/vv/RotateEulerRefFrameFilter.md @@ -38,7 +38,7 @@ 1. **Orientation library**: OrientationLib `DOrientTransformsType::{ax2om, eu2om, om2eu}` → EbsdLib `ebsdlib::{AxisAngleDType, EulerDType, OrientationMatrixDType}`. EbsdLib is the direct descendant of OrientationLib; same Rowenhorst-convention equations (`epsijk = +1`). No output change (confirmed by A/B). 2. **Matrix math**: hand-rolled `MatrixMath::Multiply3x3with3x3D` + `Normalize3x3D` → Eigen row-major multiply + `colwise().normalized()`. Semantically identical column-wise normalization; legacy's per-entry `>1` clamp is unreachable except through rounding. No output change. 3. **Degree→radian conversion precision**: legacy computes `float rotAngle = angle * pi / 180.0` in **float** before the double-precision transforms; SIMPLNX converts in **double**. ULP-level output difference only (see non-deviation N1). -4. **Progress reporting + cancel checking**: legacy has neither; SIMPLNX adds `ProgressMessageHelper`/`ProgressMessenger` (throttled) and per-element `m_ShouldCancel` checks. UX-only. +4. **Progress reporting + cancel checking**: legacy has neither; SIMPLNX adds throttled progress feedback and per-element `m_ShouldCancel` checks. UX-only. 5. **Zero-axis preflight guard (added this V&V cycle)**: preflight error `-96200` for a zero-length rotation axis, which previously NaN-corrupted the array silently in both codebases. Behavior change only for invalid input. *Material PRs since baseline:* none identified for this filter beyond routine EbsdLib version bumps and the progress-messaging framework migration. diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/AlignSectionsFeatureCentroid.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/AlignSectionsFeatureCentroid.cpp index e6adcc7ba8..0893e42035 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/AlignSectionsFeatureCentroid.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/AlignSectionsFeatureCentroid.cpp @@ -69,7 +69,7 @@ Result<> AlignSectionsFeatureCentroid::findShifts(std::vector& xShifts, std::vector xCentroid(dims[2], 0.0f); std::vector yCentroid(dims[2], 0.0f); - ThrottledMessenger throttledMessenger = getMessageHelper().createThrottledMessenger(); + ThrottledMessageHandler throttledMessenger(getMessageHandler()); // Loop over the Z Direction for(size_t iter = 0; iter < dims[2]; iter++) { @@ -77,7 +77,7 @@ Result<> AlignSectionsFeatureCentroid::findShifts(std::vector& xShifts, { return {}; } - throttledMessenger.sendThrottledMessage([&]() { return fmt::format("Determining Shifts || {:.2f}% Complete", CalculatePercentComplete(iter, dims[2])); }); + throttledMessenger.updatePercent("Determining Shifts", iter, dims[2]); size_t count = 0; xCentroid[iter] = 0; @@ -140,7 +140,7 @@ Result<> AlignSectionsFeatureCentroid::findShifts(std::vector& xShifts, std::string message = fmt::format("A shift was greater than the X dimension of the Image Geometry. " "All subsequent slices are probably wrong. Slice={} X Dim={} X Shift={} sDims[0]={}", iter, dims[0], xShifts[iter], sdims[0]); - m_MessageHandler(nx::core::IFilter::Message::Type::Info, message); + m_MessageHandler.sendInfoMessage(message); xWarning = true; } if((yShifts[iter] < -sdims[1] || yShifts[iter] > sdims[1]) && !yWarning) @@ -148,19 +148,19 @@ Result<> AlignSectionsFeatureCentroid::findShifts(std::vector& xShifts, std::string message = fmt::format("A shift was greater than the Y dimension of the Image Geometry. " "All subsequent slices are probably wrong. Slice={} Y Dim={} Y Shift={} sDims[1]={}", iter, dims[1], yShifts[iter], sdims[1]); - m_MessageHandler(nx::core::IFilter::Message::Type::Info, message); + m_MessageHandler.sendInfoMessage(message); yWarning = true; } if(std::isnan(xCentroid[iter]) && !xWarning) { std::string message = fmt::format("The X Centroid was NaN. All subsequent slices are probably wrong. Slice=", iter); - m_MessageHandler(nx::core::IFilter::Message::Type::Info, message); + m_MessageHandler.sendInfoMessage(message); xWarning = true; } if(std::isnan(yCentroid[iter]) && !yWarning) { std::string message = fmt::format("The Y Centroid was NaN. All subsequent slices are probably wrong. Slice=", iter); - m_MessageHandler(nx::core::IFilter::Message::Type::Info, message); + m_MessageHandler.sendInfoMessage(message); yWarning = true; } @@ -197,7 +197,7 @@ Result<> AlignSectionsFeatureCentroid::findShifts(std::vector& xShifts, std::string message = fmt::format("A shift was greater than the X dimension of the Image Geometry. " "All subsequent slices are probably wrong. Slice={} X Dim={} X Shift={} sDims[0]={}", iter, dims[0], xShifts[iter], sdims[0]); - m_MessageHandler(nx::core::IFilter::Message::Type::Info, message); + m_MessageHandler.sendInfoMessage(message); xWarning = true; } if((yShifts[iter] < -sdims[1] || yShifts[iter] > sdims[1]) && !yWarning) @@ -205,19 +205,19 @@ Result<> AlignSectionsFeatureCentroid::findShifts(std::vector& xShifts, std::string message = fmt::format("A shift was greater than the Y dimension of the Image Geometry. " "All subsequent slices are probably wrong. Slice={} Y Dim={} Y Shift={} sDims[1]={}", iter, dims[1], yShifts[iter], sdims[1]); - m_MessageHandler(nx::core::IFilter::Message::Type::Info, message); + m_MessageHandler.sendInfoMessage(message); yWarning = true; } if(std::isnan(xCentroid[iter]) && !xWarning) { std::string message = fmt::format("The X Centroid was NaN. All subsequent slices are probably wrong. Slice=", iter); - m_MessageHandler(nx::core::IFilter::Message::Type::Info, message); + m_MessageHandler.sendInfoMessage(message); xWarning = true; } if(std::isnan(yCentroid[iter]) && !yWarning) { std::string message = fmt::format("The Y Centroid was NaN. All subsequent slices are probably wrong. Slice=", iter); - m_MessageHandler(nx::core::IFilter::Message::Type::Info, message); + m_MessageHandler.sendInfoMessage(message); yWarning = true; } } diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/AppendImageGeometry.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/AppendImageGeometry.cpp index a3b344da03..727e95a777 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/AppendImageGeometry.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/AppendImageGeometry.cpp @@ -149,7 +149,7 @@ Result<> AppendImageGeometry::operator()() if(m_InputValues->SaveAsNewGeometry) { - m_MessageHandler(fmt::format("Combining data into array {}", newCellDataPath.createChildPath(name).toString())); + m_MessageHandler.sendInfoMessage(fmt::format("Combining data into array {}", newCellDataPath.createChildPath(name).toString())); auto newGeometry = m_DataStructure.getDataRefAs(m_InputValues->NewGeometryPath); auto newDestGeomDimsVec = newGeometry.getDimensions().toContainer>(); std::reverse(newDestGeomDimsVec.begin(), newDestGeomDimsVec.end()); @@ -157,7 +157,7 @@ Result<> AppendImageGeometry::operator()() } else { - m_MessageHandler(fmt::format("Appending data into array {}", newCellDataPath.createChildPath(name).toString())); + m_MessageHandler.sendInfoMessage(fmt::format("Appending data into array {}", newCellDataPath.createChildPath(name).toString())); auto originalDestGeomDimsVec = destGeomDims.toContainer>(); std::reverse(originalDestGeomDimsVec.begin(), originalDestGeomDimsVec.end()); auto newDestGeomDimsVec = destGeometry.getDimensions().toContainer>(); diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ApplyTransformationToGeometry.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ApplyTransformationToGeometry.cpp index a8606acccf..4386e49e7d 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ApplyTransformationToGeometry.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ApplyTransformationToGeometry.cpp @@ -102,14 +102,14 @@ Result<> ApplyTransformationToGeometry::applyImageGeometryTransformation() if(m_InputValues->InterpolationSelection == detail::k_NearestNeighborInterpolationIdx) { - m_MessageHandler(fmt::format("Applying Transform || Nearest Neighbor Interpolation {}", srcDataObject->getName())); + m_MessageHandler.sendInfoMessage(fmt::format("Applying Transform || Nearest Neighbor Interpolation {}", srcDataObject->getName())); ExecuteParallelFunction(srcDataArrayPtr->getDataType(), taskRunner, srcDataArrayPtr, destDataArrayPtr, rotateArgs, m_TransformationMatrix, false, &filterProgressCallback); } else if(m_InputValues->InterpolationSelection == detail::k_LinearInterpolationIdx) { - m_MessageHandler(fmt::format("Applying Transform || Trilinear Interpolation {}", srcDataObject->getName())); + m_MessageHandler.sendInfoMessage(fmt::format("Applying Transform || Trilinear Interpolation {}", srcDataObject->getName())); ExecuteParallelFunction(srcDataArrayPtr->getDataType(), taskRunner, srcDataArrayPtr, destDataArrayPtr, rotateArgs, m_TransformationMatrix, &filterProgressCallback); diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/CalculateTriangleGroupCurvatures.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/CalculateTriangleGroupCurvatures.cpp index c33cf8ad79..e0d821b694 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/CalculateTriangleGroupCurvatures.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/CalculateTriangleGroupCurvatures.cpp @@ -118,8 +118,7 @@ CalculateTriangleGroupCurvatures::CalculateTriangleGroupCurvatures(FeatureFaceCu Float64Array* principleCurvature1, Float64Array* principleCurvature2, Float64Array* principleDirection1, Float64Array* principleDirection2, Float64Array* gaussianCurvature, Float64Array* meanCurvature, Float64Array* weingartenMatrix, TriangleGeom* trianglesGeom, Int32Array* surfaceMeshFaceLabels, Float64Array* surfaceMeshFaceNormals, - Float64Array* surfaceMeshTriangleCentroids, const IFilter::MessageHandler& messageHandler, const std::atomic_bool& shouldCancel, - ProgressMessageHelper& progressMessageHelper) + Float64Array* surfaceMeshTriangleCentroids, const IFilter::MessageHandler& messageHandler, const std::atomic_bool& shouldCancel) : m_Filter(filter) , m_NRing(nring) , m_TriangleIds(std::move(triangleIds)) @@ -137,7 +136,6 @@ CalculateTriangleGroupCurvatures::CalculateTriangleGroupCurvatures(FeatureFaceCu , m_SurfaceMeshTriangleCentroids(surfaceMeshTriangleCentroids) , m_MessageHandler(messageHandler) , m_ShouldCancel(shouldCancel) -, m_ProgressMessageHelper(progressMessageHelper) { } @@ -165,8 +163,6 @@ void CalculateTriangleGroupCurvatures::operator()() const return; } - ProgressMessenger progressMessenger = m_ProgressMessageHelper.createProgressMessenger(); - // Instantiate a FindNRingNeighbors class to use during the loop auto& faceLabels = m_SurfaceMeshFaceLabels->getDataStoreRef(); usize triangleIdOffset = m_TriangleIds[0] * 2; @@ -398,6 +394,6 @@ void CalculateTriangleGroupCurvatures::operator()() const } // End Loop over this triangle // Send some feedback - progressMessenger.sendProgressMessage(1, [&](usize currentProgress, usize maxProgress) { return fmt::format("{}/{}", currentProgress, maxProgress); }); + m_Filter->sendThreadSafeProgressMessage(1); } } // namespace nx::core diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/CalculateTriangleGroupCurvatures.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/CalculateTriangleGroupCurvatures.hpp index fb12deb1b9..5816d20400 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/CalculateTriangleGroupCurvatures.hpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/CalculateTriangleGroupCurvatures.hpp @@ -40,7 +40,7 @@ #include "simplnx/DataStructure/DataArray.hpp" #include "simplnx/DataStructure/Geometry/TriangleGeom.hpp" #include "simplnx/Filter/IFilter.hpp" -#include "simplnx/Utilities/MessageHelper.hpp" +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" #include "SimplnxCore/Filters/Algorithms/FeatureFaceCurvature.hpp" #include "SimplnxCore/SimplnxCore_export.hpp" @@ -58,8 +58,7 @@ class SIMPLNXCORE_EXPORT CalculateTriangleGroupCurvatures CalculateTriangleGroupCurvatures(FeatureFaceCurvature* filter, int64_t nring, std::vector triangleIds, bool useNormalsForCurveFitting, Float64Array* principleCurvature1, Float64Array* principleCurvature2, Float64Array* principleDirection1, Float64Array* principleDirection2, Float64Array* gaussianCurvature, Float64Array* meanCurvature, Float64Array* weingartenMatrix, TriangleGeom* trianglesGeom, Int32Array* surfaceMeshFaceLabels, Float64Array* surfaceMeshFaceNormals, - Float64Array* surfaceMeshTriangleCentroids, const IFilter::MessageHandler& messageHandler, const std::atomic_bool& shouldCancel, - ProgressMessageHelper& progressMessageHelper); + Float64Array* surfaceMeshTriangleCentroids, const IFilter::MessageHandler& messageHandler, const std::atomic_bool& shouldCancel); virtual ~CalculateTriangleGroupCurvatures(); @@ -85,6 +84,5 @@ class SIMPLNXCORE_EXPORT CalculateTriangleGroupCurvatures Float64Array* m_SurfaceMeshTriangleCentroids; const IFilter::MessageHandler& m_MessageHandler; const std::atomic_bool& m_ShouldCancel; - ProgressMessageHelper& m_ProgressMessageHelper; }; } // namespace nx::core diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/CombineNodeBasedGeometries.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/CombineNodeBasedGeometries.cpp index f58d7050b4..029ea75ffc 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/CombineNodeBasedGeometries.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/CombineNodeBasedGeometries.cpp @@ -152,7 +152,7 @@ Result<> CombineVertexElements(DataStructure& ds, const DataPath& outputGeomPath auto* outputGeom0d = ds.getDataAs(outputGeomPath); // Combine the data - msgHandler({IFilter::Message::Type::Info, fmt::format("Combining vertex data...")}); + msgHandler.sendInfoMessage(fmt::format("Combining vertex data...")); std::vector inputGeoms(inputGeometryPaths.size()); std::transform(inputGeometryPaths.begin(), inputGeometryPaths.end(), inputGeoms.begin(), [&ds](const DataPath& path) { return ds.getDataAs(path); }); return CombineGeometryElements(ds, outputGeom0d, inputGeoms, getVerticesArrayFunc, INodeGeometry0D::k_SharedVertexListName, getVertexAttrMatrixFunc, msgHandler, shouldCancel); @@ -206,7 +206,7 @@ Result<> CombineEdgeElements(DataStructure& ds, const DataPath& outputGeomPath, std::transform(inputGeometryPaths.begin(), inputGeometryPaths.end(), inputGeoms.begin(), [&ds](const DataPath& path) { return ds.getDataAs(path); }); // Combine the data - msgHandler({IFilter::Message::Type::Info, fmt::format("Combining edge data...")}); + msgHandler.sendInfoMessage(fmt::format("Combining edge data...")); auto result = CombineGeometryElements(ds, outputGeom1d, inputGeoms, getEdgesArrayFunc, INodeGeometry1D::k_SharedEdgeListName, getEdgeAttrMatrixFunc, msgHandler, shouldCancel); if(result.invalid()) { @@ -215,7 +215,7 @@ Result<> CombineEdgeElements(DataStructure& ds, const DataPath& outputGeomPath, // Update the edges array values. For example, an edge geometry will need its edges // array updated since all the vertex indices will be different after concatenation - msgHandler({IFilter::Message::Type::Info, fmt::format("Updating edge values to use new vertex indices...")}); + msgHandler.sendInfoMessage(fmt::format("Updating edge values to use new vertex indices...")); auto getVerticesArrayFunc = [](INodeGeometry1D * ptr) -> auto { return ptr->getVertices(); @@ -269,7 +269,7 @@ Result<> CombineFaceElements(DataStructure& ds, const DataPath& outputGeomPath, } // Combine the data - msgHandler({IFilter::Message::Type::Info, fmt::format("Combining face data...")}); + msgHandler.sendInfoMessage(fmt::format("Combining face data...")); std::vector inputGeoms(inputGeometryPaths.size()); std::transform(inputGeometryPaths.begin(), inputGeometryPaths.end(), inputGeoms.begin(), [&ds](const DataPath& path) { return ds.getDataAs(path); }); auto result = CombineGeometryElements(ds, outputGeom2d, inputGeoms, getFacesArrayFunc, INodeGeometry2D::k_SharedFacesListName, getFaceAttrMatrixFunc, msgHandler, shouldCancel); @@ -280,7 +280,7 @@ Result<> CombineFaceElements(DataStructure& ds, const DataPath& outputGeomPath, // Update the faces array values. For example, a triangle geometry will need its faces // array updated since all the vertex indices will be different after concatenation - msgHandler({IFilter::Message::Type::Info, fmt::format("Updating face values to use new vertex indices...")}); + msgHandler.sendInfoMessage(fmt::format("Updating face values to use new vertex indices...")); auto getVerticesArrayFunc = [](INodeGeometry2D * ptr) -> auto { return ptr->getVertices(); @@ -334,7 +334,7 @@ Result<> CombinePolyElements(DataStructure& ds, const DataPath& outputGeomPath, } // Combine the data - msgHandler({IFilter::Message::Type::Info, fmt::format("Combining polyhedron data...")}); + msgHandler.sendInfoMessage(fmt::format("Combining polyhedron data...")); std::vector inputGeoms(inputGeometryPaths.size()); std::transform(inputGeometryPaths.begin(), inputGeometryPaths.end(), inputGeoms.begin(), [&ds](const DataPath& path) { return ds.getDataAs(path); }); auto result = CombineGeometryElements(ds, outputGeom3d, inputGeoms, getPolyArrayFunc, INodeGeometry3D::k_SharedPolyhedronListName, getPolyAttrMatrixFunc, msgHandler, shouldCancel); @@ -345,7 +345,7 @@ Result<> CombinePolyElements(DataStructure& ds, const DataPath& outputGeomPath, // Update the polyhedra array values. For example, a tetrahedral geometry will need its // polyhedra array updated since all the vertex indices will be different after concatenation - msgHandler({IFilter::Message::Type::Info, fmt::format("Updating polyhedron values to use new vertex indices...")}); + msgHandler.sendInfoMessage(fmt::format("Updating polyhedron values to use new vertex indices...")); auto getVerticesArrayFunc = [](INodeGeometry3D * ptr) -> auto { return ptr->getVertices(); @@ -377,7 +377,7 @@ const std::atomic_bool& CombineNodeBasedGeometries::getCancel() // ----------------------------------------------------------------------------- void CombineNodeBasedGeometries::sendMessage(const std::string& message) { - m_MessageHandler(IFilter::Message::Type::Info, message); + m_MessageHandler.sendInfoMessage(message); } // ----------------------------------------------------------------------------- diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/CombineStlFiles.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/CombineStlFiles.cpp index 4e693f2525..d936e2a431 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/CombineStlFiles.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/CombineStlFiles.cpp @@ -117,7 +117,7 @@ Result<> CombineStlFiles::operator()() fileListStrArray[currentIndex] = stlFilePath; activeArray[currentIndex] = 1; - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("({}/{}) Reading {}", currentIndex, paths.size(), stlFilePath)); + m_MessageHandler.sendInfoMessage(fmt::format("({}/{}) Reading {}", currentIndex, paths.size(), stlFilePath)); currentIndex++; ReadStlFileFilter stlFileReader; @@ -171,7 +171,7 @@ Result<> CombineStlFiles::operator()() usize faceLabelOffset = 0; usize vertexLabelOffset = 0; - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Moving final triangle geometry data...")); + m_MessageHandler.sendInfoMessage(fmt::format("Moving final triangle geometry data...")); // Loop over each temp geometry and copy the data into the destination geometry for(auto* currentGeometry : stlGeometries) diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeArrayHistogram.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeArrayHistogram.cpp index b416d85969..8fe407e318 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeArrayHistogram.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeArrayHistogram.cpp @@ -29,7 +29,7 @@ ComputeArrayHistogram::~ComputeArrayHistogram() noexcept = default; // ----------------------------------------------------------------------------- void ComputeArrayHistogram::updateProgress(const std::string& progressMessage) { - m_MessageHandler({IFilter::Message::Type::Info, progressMessage}); + m_MessageHandler.sendInfoMessage(progressMessage); } // ----------------------------------------------------------------------------- diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeArrayHistogramByFeature.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeArrayHistogramByFeature.cpp index 1f4acc0f57..8689ea175b 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeArrayHistogramByFeature.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeArrayHistogramByFeature.cpp @@ -5,10 +5,10 @@ #include "simplnx/DataStructure/DataGroup.hpp" #include "simplnx/DataStructure/INeighborList.hpp" #include "simplnx/Utilities/HistogramUtilities.hpp" -#include "simplnx/Utilities/MessageHelper.hpp" #include "simplnx/Utilities/ParallelAlgorithmUtilities.hpp" #include "simplnx/Utilities/ParallelDataAlgorithm.hpp" #include "simplnx/Utilities/ParallelTaskAlgorithm.hpp" +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" #include @@ -38,7 +38,7 @@ class GenerateFeatureHistogramImpl GenerateFeatureHistogramImpl(const AbstractDataStore& inputStore, AbstractDataStore& binRangesStore, NeighborList* modalBinRangesList, const AbstractDataStore& featureIdsStore, float64 histMin, float64 histMax, bool histFullRange, const std::atomic_bool& shouldCancel, const int32 numBins, AbstractDataStore& histogramStore, AbstractDataStore& mostPopulatedStore, const std::unique_ptr& mask, - std::atomic& overflow, ProgressMessageHelper& progressMessageHelper) + std::atomic& overflow, ComputeArrayHistogramByFeature* filter) : m_InputStore(inputStore) , m_ShouldCancel(shouldCancel) , m_NumBins(numBins) @@ -52,14 +52,14 @@ class GenerateFeatureHistogramImpl , m_FeatureIdsStore(featureIdsStore) , m_Mask(mask) , m_Overflow(overflow) - , m_ProgressMessageHelper(progressMessageHelper) + , m_Filter(filter) { } GenerateFeatureHistogramImpl(const AbstractDataStore& inputStore, AbstractDataStore& binRangesStore, const AbstractDataStore& featureIdsStore, float64 histMin, float64 histMax, bool histFullRange, const std::atomic_bool& shouldCancel, const int32 numBins, AbstractDataStore& histogramStore, AbstractDataStore& mostPopulatedStore, const std::unique_ptr& mask, std::atomic& overflow, - ProgressMessageHelper& progressMessageHelper) + ComputeArrayHistogramByFeature* filter) : m_InputStore(inputStore) , m_ShouldCancel(shouldCancel) , m_NumBins(numBins) @@ -73,7 +73,7 @@ class GenerateFeatureHistogramImpl , m_FeatureIdsStore(featureIdsStore) , m_Mask(mask) , m_Overflow(overflow) - , m_ProgressMessageHelper(progressMessageHelper) + , m_Filter(filter) { } @@ -90,7 +90,6 @@ class GenerateFeatureHistogramImpl void compute(usize start, usize end) const { - ProgressMessenger progressMessenger = m_ProgressMessageHelper.createProgressMessenger(); const usize numTuples = m_FeatureIdsStore.getNumberOfTuples(); const usize numCurrentFeatures = end - start; @@ -213,14 +212,13 @@ class GenerateFeatureHistogramImpl progressCount++; if(progressCount > progressIncrement) { - progressMessenger.sendProgressMessage(progressCount, - [&](usize currentProgress, usize maxProgress) { return fmt::format("Calculating feature histograms {}/{}", currentProgress, maxProgress); }); + m_Filter->sendThreadSafeProgressMessage(progressCount); progressCount = 0; } } // Send one at the end so that the progress is communicated properly - progressMessenger.sendProgressMessage(progressCount); + m_Filter->sendThreadSafeProgressMessage(progressCount); } private: @@ -237,7 +235,7 @@ class GenerateFeatureHistogramImpl AbstractDataStore& m_MostPopulatedStore; NeighborList* m_ModalBinRangesList; std::atomic& m_Overflow; - ProgressMessageHelper& m_ProgressMessageHelper; + ComputeArrayHistogramByFeature* m_Filter = nullptr; }; /** @@ -268,12 +266,20 @@ ComputeArrayHistogramByFeature::ComputeArrayHistogramByFeature(DataStructure& da , m_InputValues(inputValues) , m_ShouldCancel(shouldCancel) , m_MessageHandler(msgHandler) +, m_Throttle(msgHandler) { } // ----------------------------------------------------------------------------- ComputeArrayHistogramByFeature::~ComputeArrayHistogramByFeature() noexcept = default; +// ----------------------------------------------------------------------------- +void ComputeArrayHistogramByFeature::sendThreadSafeProgressMessage(usize counter) +{ + std::lock_guard guard(m_ProgressMessage_Mutex); + m_Throttle.incrementCount(counter); +} + // ----------------------------------------------------------------------------- Result<> ComputeArrayHistogramByFeature::operator()() { @@ -286,8 +292,6 @@ Result<> ComputeArrayHistogramByFeature::operator()() usize numFeatures = *std::max_element(featureIdsStore.begin(), featureIdsStore.end()) + 1; - MessageHelper messageHelper(m_MessageHandler); - for(int32 i = 0; i < selectedArrayPaths.size(); i++) { if(m_ShouldCancel) @@ -315,8 +319,7 @@ Result<> ComputeArrayHistogramByFeature::operator()() bool histFullRange = !m_InputValues->UserDefinedRange; - ProgressMessageHelper progressMessageHelper = messageHelper.createProgressMessageHelper(); - progressMessageHelper.setMaxProgresss(numFeatures); + m_Throttle.reset(numFeatures, "Calculating feature histograms"); if(m_InputValues->CreatedBinModalRangesDataPaths.has_value()) { @@ -325,19 +328,19 @@ Result<> ComputeArrayHistogramByFeature::operator()() modalBinRanges->resizeTuples({numFeatures}); ExecuteParallelFunctor(InstantiateHistogramByFeatureImplFunctor{}, inputData->getDataType(), dataAlg, modalBinRanges, inputData, binRanges, featureIdsStore, m_InputValues->MinRange, m_InputValues->MaxRange, histFullRange, m_ShouldCancel, - numBins, counts, mostPopulated, mask, overflow, progressMessageHelper); + numBins, counts, mostPopulated, mask, overflow, this); } else { ExecuteParallelFunctor(InstantiateHistogramByFeatureImplFunctor{}, inputData->getDataType(), dataAlg, inputData, binRanges, featureIdsStore, m_InputValues->MinRange, m_InputValues->MaxRange, - histFullRange, m_ShouldCancel, numBins, counts, mostPopulated, mask, overflow, progressMessageHelper); + histFullRange, m_ShouldCancel, numBins, counts, mostPopulated, mask, overflow, this); } - messageHelper.sendMessage(fmt::format("Calculated {} feature histograms!", numFeatures)); + m_MessageHandler.sendInfoMessage(fmt::format("Calculated {} feature histograms!", numFeatures)); if(overflow > 0) { - messageHelper.sendMessage(fmt::format("{} values not categorized into bin for array {}", overflow.load(), inputData->getName())); + m_MessageHandler.sendInfoMessage(fmt::format("{} values not categorized into bin for array {}", overflow.load(), inputData->getName())); } } diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeArrayHistogramByFeature.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeArrayHistogramByFeature.hpp index 93c99e45ac..8b6d5fef67 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeArrayHistogramByFeature.hpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeArrayHistogramByFeature.hpp @@ -5,7 +5,10 @@ #include "simplnx/DataStructure/DataPath.hpp" #include "simplnx/DataStructure/DataStructure.hpp" #include "simplnx/Filter/IFilter.hpp" +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" + #include "simplnx/Parameters/MultiArraySelectionParameter.hpp" +#include namespace nx::core { @@ -42,10 +45,18 @@ class SIMPLNXCORE_EXPORT ComputeArrayHistogramByFeature Result<> operator()(); + /** + * @brief Thread-safe progress update. Safe to call from ParallelDataAlgorithm workers. + * @param counter Features completed since the previous call + */ + void sendThreadSafeProgressMessage(usize counter); + private: DataStructure& m_DataStructure; const ComputeArrayHistogramByFeatureInputValues* m_InputValues = nullptr; const std::atomic_bool& m_ShouldCancel; const IFilter::MessageHandler& m_MessageHandler; + mutable std::mutex m_ProgressMessage_Mutex; + ThrottledMessageHandler m_Throttle; }; } // namespace nx::core diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeArrayStatistics.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeArrayStatistics.cpp index dc364fca6b..e75a49fb4b 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeArrayStatistics.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeArrayStatistics.cpp @@ -6,8 +6,8 @@ #include "simplnx/Utilities/HistogramUtilities.hpp" #include "simplnx/Utilities/MaskCompareUtilities.hpp" #include "simplnx/Utilities/Math/StatisticsCalculations.hpp" -#include "simplnx/Utilities/MessageHelper.hpp" #include "simplnx/Utilities/ParallelDataAlgorithm.hpp" +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" #include #include @@ -47,7 +47,7 @@ class StatisticsByFeatureImpl StatisticsByFeatureImpl(bool length, bool min, bool max, bool mean, bool mode, bool stdDeviation, bool summation, const std::unique_ptr& mask, const Int32AbstractDataStore& featureIds, const AbstractDataStore& source, BoolArray* featureHasDataArray, UInt64Array* lengthArray, DataArray* minArray, DataArray* maxArray, Float32Array* meanArray, NeighborList* modeArray, Float32Array* stdDevArray, Float32Array* summationArray, const std::atomic_bool& shouldCancel, - MessageHelper& messageHelper) + ComputeArrayStatistics* filter) : m_Length(length) , m_Min(min) , m_Max(max) @@ -67,18 +67,17 @@ class StatisticsByFeatureImpl , m_StdDevArray(stdDevArray) , m_SummationArray(summationArray) , m_ShouldCancel(shouldCancel) - , m_MessageHelper(messageHelper) + , m_Filter(filter) { } void compute(usize start, usize end) const { - ThrottledMessenger throttledMessenger = m_MessageHelper.createThrottledMessenger(); const usize numTuples = m_FeatureIds.getNumberOfTuples(); const usize numCurrentFeatures = end - start; - auto msgHandler = [this](const std::string& msg) { m_MessageHelper.trySendMessage("Preparing features/ensembles for stats calculation " + msg); }; + auto msgHandler = [this](const std::string& msg) { m_Filter->sendThreadSafeProgressMessage([&] { return "Preparing features/ensembles for stats calculation " + msg; }); }; auto [length, min, max, summation, modalMaps] = HistogramUtilities::concurrent::CalculateFeatureHasDataStats(m_Source, m_FeatureIds, start, end, m_Mask, msgHandler, m_ShouldCancel); if(m_ShouldCancel) { @@ -88,7 +87,7 @@ class StatisticsByFeatureImpl usize progressCount = 0; usize progressIncrement = numCurrentFeatures / 100; - m_MessageHelper.sendMessage(fmt::format("Calculating statistics for feature range [{}-{}]", start, end)); + m_Filter->sendThreadSafeInfoMessage(fmt::format("Calculating statistics for feature range [{}-{}]", start, end)); std::vector meanArray; if(m_StdDeviation && !m_Mean) @@ -165,7 +164,7 @@ class StatisticsByFeatureImpl progressCount++; if(progressCount > progressIncrement) { - throttledMessenger.sendThrottledMessage([&]() { + m_Filter->sendThreadSafeProgressMessage([&]() { progressCount = 0; return fmt::format("Calculating statistics for feature [{}-{}] {}/{}", start, end, j, end); }); @@ -175,7 +174,7 @@ class StatisticsByFeatureImpl if(m_StdDeviation) { // https://www.khanacademy.org/math/statistics-probability/summarizing-quantitative-data/variance-standard-deviation-population/a/calculating-standard-deviation-step-by-step - m_MessageHelper.sendMessage(fmt::format("Computing StdDev Feature/Ensemble [{}-{}]", start, end)); + m_Filter->sendThreadSafeInfoMessage(fmt::format("Computing StdDev Feature/Ensemble [{}-{}]", start, end)); // This should probably be done with Kahan Summation instead std::vector sumOfDiffs(numCurrentFeatures, 0.0f); progressCount = 0; @@ -204,7 +203,7 @@ class StatisticsByFeatureImpl progressCount++; if(progressCount > progressIncrement) { - throttledMessenger.sendThrottledMessage([&]() { + m_Filter->sendThreadSafeProgressMessage([&]() { progressCount = 0; return fmt::format("StdDev Calculation Feature/Ensemble [{}-{}]: {:.2f}%", start, end, 100.0f * static_cast(tupleIndex) / static_cast(numTuples)); }); @@ -246,7 +245,7 @@ class StatisticsByFeatureImpl Float32Array* m_StdDevArray = nullptr; Float32Array* m_SummationArray = nullptr; const std::atomic_bool& m_ShouldCancel; - MessageHelper& m_MessageHelper; + ComputeArrayStatistics* m_Filter = nullptr; }; template @@ -256,7 +255,7 @@ class StatisticsByFeatureRangeImpl StatisticsByFeatureRangeImpl(bool length, bool min, bool max, bool mean, bool mode, bool stdDeviation, bool summation, const Int32AbstractDataStore& featureIdsMap, const BoolAbstractDataStore& tempMask, const Int32AbstractDataStore& featureIds, const AbstractDataStore& source, BoolArray* featureHasDataArray, UInt64Array* lengthArray, DataArray* minArray, DataArray* maxArray, Float32Array* meanArray, NeighborList* modeArray, Float32Array* stdDevArray, - Float32Array* summationArray, const std::atomic_bool& shouldCancel, MessageHelper& messageHelper) + Float32Array* summationArray, const std::atomic_bool& shouldCancel, ComputeArrayStatistics* filter) : m_Length(length) , m_Min(min) , m_Max(max) @@ -277,13 +276,12 @@ class StatisticsByFeatureRangeImpl , m_StdDevArray(stdDevArray) , m_SummationArray(summationArray) , m_ShouldCancel(shouldCancel) - , m_MessageHelper(messageHelper) + , m_Filter(filter) { } void compute(usize start, usize end) const { - ThrottledMessenger throttledMessenger = m_MessageHelper.createThrottledMessenger(); const usize numTuples = m_Source.getNumberOfTuples(); for(usize featureId = start; featureId < end; featureId++) { @@ -396,7 +394,7 @@ class StatisticsByFeatureRangeImpl m_FeatureHasDataArray->initializeTuple(truePosition, false); } - throttledMessenger.sendThrottledMessage([&]() { return fmt::format("Storing data for feature/ensembles [{}-{}] {}/{}", start, end, featureId, end); }); + m_Filter->sendThreadSafeProgressMessage([&]() { return fmt::format("Storing data for feature/ensembles [{}-{}] {}/{}", start, end, featureId, end); }); } } // end of compute @@ -426,7 +424,7 @@ class StatisticsByFeatureRangeImpl Float32Array* m_StdDevArray = nullptr; Float32Array* m_SummationArray = nullptr; const std::atomic_bool& m_ShouldCancel; - MessageHelper& m_MessageHelper; + ComputeArrayStatistics* m_Filter = nullptr; }; template @@ -434,7 +432,7 @@ class MedianByFeatureImpl { public: MedianByFeatureImpl(const std::unique_ptr& mask, const Int32AbstractDataStore& featureIds, const AbstractDataStore& source, bool findMedian, bool findNumUnique, - Float32Array* medianArray, Int32Array* numUniqueValuesArray, DataArray* lengthArray, MessageHelper& messageHelper) + Float32Array* medianArray, Int32Array* numUniqueValuesArray, DataArray* lengthArray, ComputeArrayStatistics* filter) : m_FindMedian(findMedian) , m_FindNumUniqueValues(findNumUnique) , m_MedianArray(medianArray) @@ -443,13 +441,13 @@ class MedianByFeatureImpl , m_FeatureIds(featureIds) , m_Source(source) , m_LengthArray(lengthArray) - , m_MessageHelper(messageHelper) + , m_Filter(filter) { } void compute(usize start, usize end) const { - m_MessageHelper.sendMessage(fmt::format("Starting Median Array Calculation: Feature/Ensemble [{}-{}]", start, end)); + m_Filter->sendThreadSafeInfoMessage(fmt::format("Starting Median Array Calculation: Feature/Ensemble [{}-{}]", start, end)); const usize numFeatureSources = end - start; // Create the arrays that will collect the values from the arrays. allocate them to the correct size based on the length array @@ -505,7 +503,7 @@ class MedianByFeatureImpl const Int32AbstractDataStore& m_FeatureIds; const AbstractDataStore& m_Source; const DataArray* m_LengthArray = nullptr; - MessageHelper& m_MessageHelper; + ComputeArrayStatistics* m_Filter = nullptr; }; template @@ -513,7 +511,7 @@ class MedianByFeatureRangeImpl { public: MedianByFeatureRangeImpl(const Int32AbstractDataStore& featureIdsMap, const BoolAbstractDataStore& tempMask, const Int32AbstractDataStore& featureIds, const AbstractDataStore& source, - bool findMedian, bool findNumUnique, Float32Array* medianArray, Int32Array* numUniqueValuesArray, MessageHelper& messageHelper) + bool findMedian, bool findNumUnique, Float32Array* medianArray, Int32Array* numUniqueValuesArray, ComputeArrayStatistics* filter) : m_FindMedian(findMedian) , m_FindNumUniqueValues(findNumUnique) , m_MedianArray(medianArray) @@ -522,13 +520,13 @@ class MedianByFeatureRangeImpl , m_Mask(tempMask) , m_FeatureIds(featureIds) , m_Source(source) - , m_MessageHelper(messageHelper) + , m_Filter(filter) { } void compute(usize start, usize end) const { - m_MessageHelper.sendMessage(fmt::format("Starting Median Array Calculation: Feature/Ensemble [{}-{}]", start, end)); + m_Filter->sendThreadSafeInfoMessage(fmt::format("Starting Median Array Calculation: Feature/Ensemble [{}-{}]", start, end)); const usize numTuples = m_Source.getNumberOfTuples(); for(usize featureId = start; featureId < end; featureId++) @@ -594,7 +592,7 @@ class MedianByFeatureRangeImpl const Int32AbstractDataStore& m_FeatureIds; const Int32AbstractDataStore& m_FeatureIdsMap; const AbstractDataStore& m_Source; - MessageHelper& m_MessageHelper; + ComputeArrayStatistics* m_Filter = nullptr; }; // ----------------------------------------------------------------------------- @@ -889,7 +887,7 @@ struct ComputeArrayStatisticsByFeatureFunctor { template Result<> operator()(DataStructure& dataStructure, const IDataArray* inputIDataArray, std::vector& arrays, usize numFeatures, const ComputeArrayStatisticsInputValues* inputValues, - const std::atomic_bool& shouldCancel, MessageHelper& messageHelper) + const std::atomic_bool& shouldCancel, ComputeArrayStatistics* filter) { std::unique_ptr maskCompare = nullptr; if(inputValues->UseMask) @@ -939,7 +937,7 @@ struct ComputeArrayStatisticsByFeatureFunctor auto& data = inputArrayPtr->getDataStoreRef(); StatisticsByFeatureImpl classToExecute = StatisticsByFeatureImpl(inputValues->FindLength, inputValues->FindMin, inputValues->FindMax, inputValues->FindMean, inputValues->FindMode, inputValues->FindStdDeviation, inputValues->FindSummation, maskCompare, featureIds, data, featureHasDataPtr, lengthArrayPtr, - minArrayPtr, maxArrayPtr, meanArrayPtr, modeArrayPtr, stdDevArrayPtr, summationArrayPtr, shouldCancel, messageHelper); + minArrayPtr, maxArrayPtr, meanArrayPtr, modeArrayPtr, stdDevArrayPtr, summationArrayPtr, shouldCancel, filter); if(CheckArraysInMemory(indexAlgArrays)) { const tbb::simple_partitioner simplePartitioner; @@ -957,7 +955,7 @@ struct ComputeArrayStatisticsByFeatureFunctor if(inputValues->FindMedian || inputValues->FindNumUniqueValues) { - messageHelper.sendMessage("Starting Median Calculation..."); + filter->sendThreadSafeInfoMessage("Starting Median Calculation..."); auto* medianArrayPtr = dynamic_cast(arrays[4]); auto* numUniqueValuesArrayPtr = dynamic_cast(arrays[8]); @@ -976,7 +974,7 @@ struct ComputeArrayStatisticsByFeatureFunctor } medianDataAlg.setRange(0, numFeatures); medianDataAlg.execute( - MedianByFeatureImpl(maskCompare, featureIds, data, inputValues->FindMedian, inputValues->FindNumUniqueValues, medianArrayPtr, numUniqueValuesArrayPtr, lengthArrayPtr, messageHelper)); + MedianByFeatureImpl(maskCompare, featureIds, data, inputValues->FindMedian, inputValues->FindNumUniqueValues, medianArrayPtr, numUniqueValuesArrayPtr, lengthArrayPtr, filter)); } // compute the standardized data @@ -1000,7 +998,7 @@ struct ComputeArrayStatisticsByFeatureFunctor template Result<> operator()(DataStructure& dataStructure, const IDataArray* inputIDataArray, std::vector& arrays, const std::pair& range, - const ComputeArrayStatisticsInputValues* inputValues, const std::atomic_bool& shouldCancel, MessageHelper& messageHelper) + const ComputeArrayStatisticsInputValues* inputValues, const std::atomic_bool& shouldCancel, ComputeArrayStatistics* filter) { Result<> initializationResult = InitializeArrays(dataStructure, inputValues); if(initializationResult.invalid()) @@ -1042,7 +1040,7 @@ struct ComputeArrayStatisticsByFeatureFunctor const auto& data = inputArrayPtr->getDataStoreRef(); StatisticsByFeatureRangeImpl classToExecute = StatisticsByFeatureRangeImpl( inputValues->FindLength, inputValues->FindMin, inputValues->FindMax, inputValues->FindMean, inputValues->FindMode, inputValues->FindStdDeviation, inputValues->FindSummation, featureIdsMap, - tempMask, featureIds, data, featureHasDataPtr, lengthArrayPtr, minArrayPtr, maxArrayPtr, meanArrayPtr, modeArrayPtr, stdDevArrayPtr, summationArrayPtr, shouldCancel, messageHelper); + tempMask, featureIds, data, featureHasDataPtr, lengthArrayPtr, minArrayPtr, maxArrayPtr, meanArrayPtr, modeArrayPtr, stdDevArrayPtr, summationArrayPtr, shouldCancel, filter); if(CheckArraysInMemory(indexAlgArrays)) { const tbb::simple_partitioner simplePartitioner; @@ -1060,7 +1058,7 @@ struct ComputeArrayStatisticsByFeatureFunctor if(inputValues->FindMedian || inputValues->FindNumUniqueValues) { - messageHelper.sendMessage("Starting Median Calculation..."); + filter->sendThreadSafeInfoMessage("Starting Median Calculation..."); auto* medianArrayPtr = dynamic_cast(arrays[4]); auto* numUniqueValuesArrayPtr = dynamic_cast(arrays[8]); @@ -1078,7 +1076,7 @@ struct ComputeArrayStatisticsByFeatureFunctor } medianDataAlg.setRange(range.first, range.second + 1); medianDataAlg.execute( - MedianByFeatureRangeImpl(featureIdsMap, tempMask, featureIds, data, inputValues->FindMedian, inputValues->FindNumUniqueValues, medianArrayPtr, numUniqueValuesArrayPtr, messageHelper)); + MedianByFeatureRangeImpl(featureIdsMap, tempMask, featureIds, data, inputValues->FindMedian, inputValues->FindNumUniqueValues, medianArrayPtr, numUniqueValuesArrayPtr, filter)); } // compute the standardized data based on whether computing by index or not @@ -1110,12 +1108,20 @@ ComputeArrayStatistics::ComputeArrayStatistics(DataStructure& dataStructure, con , m_InputValues(inputValues) , m_ShouldCancel(shouldCancel) , m_MessageHandler(msgHandler) +, m_Throttle(msgHandler) { } // ----------------------------------------------------------------------------- ComputeArrayStatistics::~ComputeArrayStatistics() noexcept = default; +// ----------------------------------------------------------------------------- +void ComputeArrayStatistics::sendThreadSafeInfoMessage(const std::string& message) +{ + std::lock_guard guard(m_ProgressMessage_Mutex); + m_MessageHandler.sendInfoMessage(message); +} + // ----------------------------------------------------------------------------- Result<> ComputeArrayStatistics::operator()() { @@ -1164,8 +1170,6 @@ Result<> ComputeArrayStatistics::operator()() arrays[8] = m_DataStructure.getDataAs(m_InputValues->NumUniqueValuesName); } - MessageHelper messageHelper(m_MessageHandler); - if(!m_InputValues->ComputeByIndex) { const auto& inputArray = m_DataStructure.getDataRefAs(m_InputValues->SelectedArrayPath); @@ -1220,7 +1224,7 @@ Result<> ComputeArrayStatistics::operator()() const auto* inputArray = m_DataStructure.getDataAs(m_InputValues->SelectedArrayPath); // We must use ExecuteNeighborFunction because the Mode array is a NeighborList - return ExecuteNeighborFunction(ComputeArrayStatisticsByFeatureFunctor{}, inputArray->getDataType(), m_DataStructure, inputArray, arrays, numFeatures, m_InputValues, m_ShouldCancel, messageHelper); + return ExecuteNeighborFunction(ComputeArrayStatisticsByFeatureFunctor{}, inputArray->getDataType(), m_DataStructure, inputArray, arrays, numFeatures, m_InputValues, m_ShouldCancel, this); } default: { return MakeErrorResult(-506670, fmt::format("Unknown feature id range controls option selected. Feature ID range was {} to {}.", trueMin, trueMax)); @@ -1294,5 +1298,5 @@ Result<> ComputeArrayStatistics::operator()() // We must use ExecuteNeighborFunction because the Mode array is a NeighborList return ExecuteNeighborFunction(ComputeArrayStatisticsByFeatureFunctor{}, inputArray->getDataType(), m_DataStructure, inputArray, arrays, std::make_pair(trueMin, trueMax), m_InputValues, - m_ShouldCancel, messageHelper); + m_ShouldCancel, this); } diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeArrayStatistics.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeArrayStatistics.hpp index a754fd4d90..9c3cbc6b60 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeArrayStatistics.hpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeArrayStatistics.hpp @@ -8,6 +8,7 @@ #include "simplnx/Filter/IFilter.hpp" #include "simplnx/Parameters/ChoicesParameter.hpp" #include "simplnx/Parameters/VectorParameter.hpp" +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" #include #include @@ -80,11 +81,32 @@ class SIMPLNXCORE_EXPORT ComputeArrayStatistics Result<> operator()(); + /** + * @brief Thread-safe throttled progress update. Takes a functor rather than a string because the + * callers reset a progress counter inside it, so the body must run only when a message is due. + * @param functor Callable of the form std::string func() + */ + template + requires std::is_invocable_r_v + void sendThreadSafeProgressMessage(CallableT&& functor) + { + std::lock_guard guard(m_ProgressMessage_Mutex); + m_Throttle.queueMessage(std::forward(functor)); + } + + /** + * @brief Thread-safe guaranteed status message. Safe to call from the parallel range workers. + * @param message + */ + void sendThreadSafeInfoMessage(const std::string& message); + private: DataStructure& m_DataStructure; const ComputeArrayStatisticsInputValues* m_InputValues = nullptr; const std::atomic_bool& m_ShouldCancel; const IFilter::MessageHandler& m_MessageHandler; + mutable std::mutex m_ProgressMessage_Mutex; + ThrottledMessageHandler m_Throttle; }; } // namespace nx::core diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeBiasedFeatures.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeBiasedFeatures.cpp index 933b15dd4e..7b748956f6 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeBiasedFeatures.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeBiasedFeatures.cpp @@ -81,7 +81,7 @@ Result<> ComputeBiasedFeatures::findBoundingBoxFeatures() { if(m_InputValues->CalcByPhase) { - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Working on Phase {} of {}", iter, numPhases)); + m_MessageHandler.sendInfoMessage(fmt::format("Working on Phase {} of {}", iter, numPhases)); } // reset boundBox for each phase const BoundingBox3D boundingBox = imageGeometry.getBoundingBoxf(); diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeFeatureCentroids.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeFeatureCentroids.cpp index 8cbc33fe10..91419eac99 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeFeatureCentroids.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeFeatureCentroids.cpp @@ -215,10 +215,10 @@ Result<> ComputeFeatureCentroids::operator()() if(m_InputValues->IsPeriodic) { - m_MessageHandler({IFilter::Message::Type::Info, "Checking for periodic data."}); + m_MessageHandler.sendInfoMessage("Checking for periodic data."); if(GeometryHelpers::Topology::AdjustCentroidsForPeriodicFaces(imageGeom, rangeXStoreRef, rangeYStoreRef, rangeZStoreRef, centroids)) { - m_MessageHandler({IFilter::Message::Type::Info, "ComputeFeatureCentroids found Non-Contiguous Features. Centroids may require additional checks."}); + m_MessageHandler.sendInfoMessage("ComputeFeatureCentroids found Non-Contiguous Features. Centroids may require additional checks."); } } diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeFeatureClustering.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeFeatureClustering.cpp index ada81ad5bf..2e721423b0 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeFeatureClustering.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeFeatureClustering.cpp @@ -199,7 +199,7 @@ Result<> ComputeFeatureClustering::operator()() { if(i % 1000 == 0) { - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Working on Feature {} of {}", i, totalPptFeatures)); + m_MessageHandler.sendInfoMessage(fmt::format("Working on Feature {} of {}", i, totalPptFeatures)); } x = centroidsStore[3 * i]; diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeFeatureNeighbors.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeFeatureNeighbors.cpp index 68cd087e75..81946462c2 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeFeatureNeighbors.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeFeatureNeighbors.cpp @@ -3,8 +3,8 @@ #include "simplnx/DataStructure/DataArray.hpp" #include "simplnx/DataStructure/Geometry/ImageGeom.hpp" #include "simplnx/DataStructure/NeighborList.hpp" -#include "simplnx/Utilities/MessageHelper.hpp" #include "simplnx/Utilities/NeighborUtilities.hpp" +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" using namespace nx::core; @@ -16,7 +16,7 @@ struct ComputeFeatureNeighborsFunctor template Result<> operator()(BoolAbstractDataStore* surfaceFeatures, Int8AbstractDataStore* boundaryCells, Float32NeighborList& sharedSurfaceAreaList, Int32NeighborList& neighborsList, Int32AbstractDataStore& numNeighbors, const Int32AbstractDataStore& featureIds, usize totalFeatures, const std::array& dims, const std::array spacing, - ThrottledMessenger& throttledMessenger, const std::atomic_bool& shouldCancel) const + ThrottledMessageHandler& throttledMessenger, const std::atomic_bool& shouldCancel) const { constexpr FaceNeighborType k_NeighborCount = VoxelNeighbors::k_FaceNeighborCount; @@ -206,7 +206,7 @@ struct ComputeFeatureNeighborsFunctor for(int64 yIndex = 1; yIndex < dims[1] - 1; yIndex++) { const int64 yStride = dims[0] * yIndex; - throttledMessenger.sendThrottledMessage([&] { return fmt::format("Determining Neighbor Lists || {:.2f}% Complete", CalculatePercentComplete(zStride + yStride, totalPoints)); }); + throttledMessenger.updatePercent("Determining Neighbor Lists", zStride + yStride, totalPoints); if(shouldCancel) { @@ -336,8 +336,7 @@ ComputeFeatureNeighbors::~ComputeFeatureNeighbors() noexcept = default; // ----------------------------------------------------------------------------- Result<> ComputeFeatureNeighbors::operator()() { - MessageHelper messageHelper(m_MessageHandler); - ThrottledMessenger throttledMessenger = messageHelper.createThrottledMessenger(); + ThrottledMessageHandler throttledMessenger(m_MessageHandler); auto& featureIds = m_DataStructure.getDataAs(m_InputValues->FeatureIdsPath)->getDataStoreRef(); auto& numNeighbors = m_DataStructure.getDataAs(m_InputValues->NumberOfNeighborsPath)->getDataStoreRef(); diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeFeatureSizes.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeFeatureSizes.cpp index 19202699d1..3c57cc6975 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeFeatureSizes.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeFeatureSizes.cpp @@ -7,8 +7,8 @@ #include "simplnx/DataStructure/Geometry/ImageGeom.hpp" #include "simplnx/DataStructure/Geometry/RectGridGeom.hpp" #include "simplnx/Utilities/DataArrayUtilities.hpp" -#include "simplnx/Utilities/MessageHelper.hpp" #include "simplnx/Utilities/ParallelDataAlgorithm.hpp" +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" #include @@ -79,16 +79,16 @@ class ImageSummationImpl }; Result<> ProcessImageGeom(ImageGeom& imageGeom, Float32AbstractDataStore& volumes, Float32AbstractDataStore& equivalentDiameters, Int32AbstractDataStore& numElements, - const Int32AbstractDataStore& featureIds, const bool saveElementSizes, MessageHelper& msgHelper, const std::atomic_bool& shouldCancel) + const Int32AbstractDataStore& featureIds, const bool saveElementSizes, const IFilter::MessageHandler& messageHandler, const std::atomic_bool& shouldCancel) { - ThrottledMessenger throttledMessenger = msgHelper.createThrottledMessenger(); + ThrottledMessageHandler throttle(messageHandler); const usize numFeatures = volumes.getNumberOfTuples(); SizeVec3 dims = imageGeom.getDimensions(); std::vector featureVoxelCounts(numFeatures, 0); - msgHelper.sendMessage("Finding Voxel Counts..."); + messageHandler.sendInfoMessage("Finding Voxel Counts..."); // Count and store the number of voxels in each feature FeatureVoxelCountsT threadLocalVoxelCounts([numFeatures] { return std::vector(numFeatures, 0); }); ParallelDataAlgorithm dataAlg; @@ -124,7 +124,7 @@ Result<> ProcessImageGeom(ImageGeom& imageGeom, Float32AbstractDataStore& volume // Treat dimensions of 1 as flat for image geom if(xDimSize == 1 || yDimSize == 1 || zDimSize == 1) { - msgHelper.sendMessage("Singular image detected. Proceeding with 2D calculations..."); + messageHandler.sendInfoMessage("Singular image detected. Proceeding with 2D calculations..."); // One of the dimensions is empty, so we will be calculating area instead /** @@ -156,8 +156,9 @@ Result<> ProcessImageGeom(ImageGeom& imageGeom, Float32AbstractDataStore& volume // Calculate the area of a single voxel const float64 voxelArea = static_cast(spacing[0]) * static_cast(spacing[1]) * static_cast(spacing[2]); - msgHelper.sendMessage("Feature Level: Storing Voxel Counts and Calculating Area and ECD..."); + messageHandler.sendInfoMessage("Feature Level: Storing Voxel Counts and Calculating Area and ECD..."); // Process each feature storing feature voxel counts, areas, and equivalent circular diameter + throttle.reset(numFeatures, " - Calculating"); for(usize featureIdx = 1; featureIdx < numFeatures; featureIdx++) { if(shouldCancel) @@ -171,7 +172,7 @@ Result<> ProcessImageGeom(ImageGeom& imageGeom, Float32AbstractDataStore& volume return MakeErrorResult(k_BadFeatureCount, fmt::format("Feature {} contains more voxels ({}) than the 32-bit integer limit ({}).", featureIdx, featureVoxelCounts[featureIdx], k_MaxVoxelCount)); } - throttledMessenger.sendThrottledMessage([&] { return fmt::format(" - Calculating || {:.2f}% Complete", CalculatePercentComplete(featureIdx, numFeatures)); }); + throttle.updatePercent(featureIdx); // Store the number of voxels in feature as int32 numElements.setValue(featureIdx, static_cast(featureVoxelCounts[featureIdx])); @@ -193,13 +194,14 @@ Result<> ProcessImageGeom(ImageGeom& imageGeom, Float32AbstractDataStore& volume else { // If we are here, it is an image stack and thus should be treated as 3D. - msgHelper.sendMessage("Image Stack detected. Proceeding with 3D calculations..."); + messageHandler.sendInfoMessage("Image Stack detected. Proceeding with 3D calculations..."); // Calculate the volume of a single voxel const float64 voxelVolume = spacing[0] * spacing[1] * spacing[2]; - msgHelper.sendMessage("Feature Level: Storing Voxel Counts and Calculating Volume and ESD..."); + messageHandler.sendInfoMessage("Feature Level: Storing Voxel Counts and Calculating Volume and ESD..."); // Process each feature storing feature voxel counts, volumes, and equivalent spherical diameter + throttle.reset(numFeatures, " - Calculating"); for(usize featureIdx = 1; featureIdx < numFeatures; featureIdx++) { if(shouldCancel) @@ -213,7 +215,7 @@ Result<> ProcessImageGeom(ImageGeom& imageGeom, Float32AbstractDataStore& volume return MakeErrorResult(k_BadFeatureCount, fmt::format("Feature {} contains more voxels ({}) than the 32-bit integer limit ({}).", featureIdx, featureVoxelCounts[featureIdx], k_MaxVoxelCount)); } - throttledMessenger.sendThrottledMessage([&] { return fmt::format(" - Calculating || {:.2f}% Complete", CalculatePercentComplete(featureIdx, numFeatures)); }); + throttle.updatePercent(featureIdx); // Store the number of voxels in feature as int32 numElements.setValue(featureIdx, static_cast(featureVoxelCounts[featureIdx])); @@ -235,7 +237,7 @@ Result<> ProcessImageGeom(ImageGeom& imageGeom, Float32AbstractDataStore& volume if(saveElementSizes) { - msgHelper.sendMessage("Calculating Element Sizes..."); + messageHandler.sendInfoMessage("Calculating Element Sizes..."); return imageGeom.findElementSizes(false); } @@ -312,14 +314,14 @@ class RectGridSummationImpl }; Result<> ProcessRectGridGeom(RectGridGeom& rectGridGeom, Float32AbstractDataStore& volumes, Float32AbstractDataStore& equivalentDiameters, Int32AbstractDataStore& numElements, - const Int32AbstractDataStore& featureIds, const bool saveElementSizes, MessageHelper& msgHelper, const std::atomic_bool& shouldCancel) + const Int32AbstractDataStore& featureIds, const bool saveElementSizes, const IFilter::MessageHandler& messageHandler, const std::atomic_bool& shouldCancel) { - ThrottledMessenger throttledMessenger = msgHelper.createThrottledMessenger(); + ThrottledMessageHandler throttle(messageHandler); const usize numFeatures = volumes.getNumberOfTuples(); SizeVec3 dims = rectGridGeom.getDimensions(); - msgHelper.sendMessage("Finding Element Sizes..."); + messageHandler.sendInfoMessage("Finding Element Sizes..."); Result<> result = rectGridGeom.findElementSizes(false); if(result.invalid()) { @@ -331,7 +333,7 @@ Result<> ProcessRectGridGeom(RectGridGeom& rectGridGeom, Float32AbstractDataStor std::vector featureVoxelCounts(numFeatures, 0); std::vector featureVolumes(numFeatures, 0.0); - msgHelper.sendMessage("Cell Level: Finding Voxel Counts and Summing Volumes..."); + messageHandler.sendInfoMessage("Cell Level: Finding Voxel Counts and Summing Volumes..."); // Count and store the number of voxels in each feature FeatureVoxelCountsT threadLocalVoxelCounts([numFeatures] { return std::vector(numFeatures, 0); }); FeatureVolumesT threadLocalVolumes([numFeatures] { return std::vector(numFeatures, 0); }); @@ -378,8 +380,9 @@ Result<> ProcessRectGridGeom(RectGridGeom& rectGridGeom, Float32AbstractDataStor return {}; } - msgHelper.sendMessage("Feature Level: Storing Voxel Counts and Calculating ESD..."); + messageHandler.sendInfoMessage("Feature Level: Storing Voxel Counts and Calculating ESD..."); // Process each feature storing feature voxel counts and equivalent spherical diameter + throttle.reset(numFeatures, " - Calculating"); for(usize featureIdx = 1; featureIdx < numFeatures; featureIdx++) { if(shouldCancel) @@ -387,7 +390,7 @@ Result<> ProcessRectGridGeom(RectGridGeom& rectGridGeom, Float32AbstractDataStor return {}; } - throttledMessenger.sendThrottledMessage([&] { return fmt::format(" - Calculating || {:.2f}% Complete", CalculatePercentComplete(featureIdx, numFeatures)); }); + throttle.updatePercent(featureIdx); // Check for integer overflow if(featureVoxelCounts[featureIdx] > k_MaxVoxelCount) @@ -412,7 +415,7 @@ Result<> ProcessRectGridGeom(RectGridGeom& rectGridGeom, Float32AbstractDataStor if(!saveElementSizes) { - msgHelper.sendMessage("Cleaning Up Element Sizes..."); + messageHandler.sendInfoMessage("Cleaning Up Element Sizes..."); rectGridGeom.deleteElementSizes(); } @@ -435,11 +438,10 @@ ComputeFeatureSizes::~ComputeFeatureSizes() noexcept = default; // ----------------------------------------------------------------------------- Result<> ComputeFeatureSizes::operator()() { - MessageHelper messageHelper(m_MessageHandler); const bool saveElementSizes = m_InputValues->SaveElementSizes; - messageHelper.sendMessage("Validating Feature Ids and Feature Attribute Matrix..."); + m_MessageHandler.sendInfoMessage("Validating Feature Ids and Feature Attribute Matrix..."); const DataPath featureIdsArrayPath = m_InputValues->FeatureIdsPath; const auto* featureIdsArrayPtr = m_DataStructure.getDataAs(featureIdsArrayPath); { @@ -467,15 +469,15 @@ Result<> ComputeFeatureSizes::operator()() const IGeometry::Type geomType = geom.getGeomType(); if(geomType == IGeometry::Type::Image) { - messageHelper.sendMessage("Beginning Processing Features in Image Geometry..."); + m_MessageHandler.sendInfoMessage("Beginning Processing Features in Image Geometry..."); auto& imageGeom = dynamic_cast(geom); - return ProcessImageGeom(imageGeom, volumes, equivalentDiameters, numElements, featureIds, saveElementSizes, messageHelper, m_ShouldCancel); + return ProcessImageGeom(imageGeom, volumes, equivalentDiameters, numElements, featureIds, saveElementSizes, m_MessageHandler, m_ShouldCancel); } if(geomType == IGeometry::Type::RectGrid) { - messageHelper.sendMessage("Beginning Processing Features in Rectilinear Grid Geometry..."); + m_MessageHandler.sendInfoMessage("Beginning Processing Features in Rectilinear Grid Geometry..."); auto& rectGridGeom = dynamic_cast(geom); - return ProcessRectGridGeom(rectGridGeom, volumes, equivalentDiameters, numElements, featureIds, saveElementSizes, messageHelper, m_ShouldCancel); + return ProcessRectGridGeom(rectGridGeom, volumes, equivalentDiameters, numElements, featureIds, saveElementSizes, m_MessageHandler, m_ShouldCancel); } return {}; diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeGroupingDensity.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeGroupingDensity.cpp index 44f859997f..e0996df3cf 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeGroupingDensity.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeGroupingDensity.cpp @@ -2,7 +2,7 @@ #include "simplnx/DataStructure/DataArray.hpp" #include "simplnx/DataStructure/NeighborList.hpp" -#include "simplnx/Utilities/MessageHelper.hpp" +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" #include @@ -77,13 +77,12 @@ class FindDensityGrouping // Default value-initialized to zeroes: https://en.cppreference.com/w/cpp/named_req/DefaultInsertable checkedFeatureVolumes.resize(numFeatures); } - MessageHelper messageHelper(m_MessageHandler); - ThrottledMessenger throttledMessenger = messageHelper.createThrottledMessenger(); + ThrottledMessageHandler throttledMessenger(m_MessageHandler); // Start the Parent Outer Loop for(usize currentParentId = 1; currentParentId < numParents; currentParentId++) { - throttledMessenger.sendThrottledMessage([&]() { return fmt::format("{}/{} {:.1f}%", currentParentId, numParents, CalculatePercentComplete(currentParentId, numParents)); }); + throttledMessenger.updateCount("Computing grouping density", currentParentId, numParents); if(m_ShouldCancel) { diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeKMeans.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeKMeans.cpp index c39457655b..9974daee8c 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeKMeans.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeKMeans.cpp @@ -195,7 +195,7 @@ ComputeKMeans::~ComputeKMeans() noexcept = default; // ----------------------------------------------------------------------------- void ComputeKMeans::updateProgress(const std::string& message) { - m_MessageHandler(IFilter::Message::Type::Info, message); + m_MessageHandler.sendInfoMessage(message); } // ----------------------------------------------------------------------------- diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeKMedoids.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeKMedoids.cpp index 62c870f18b..af3f7564b1 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeKMedoids.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeKMedoids.cpp @@ -196,7 +196,7 @@ ComputeKMedoids::~ComputeKMedoids() noexcept = default; // ----------------------------------------------------------------------------- void ComputeKMedoids::updateProgress(const std::string& message) { - m_MessageHandler(IFilter::Message::Type::Info, message); + m_MessageHandler.sendInfoMessage(message); } // ----------------------------------------------------------------------------- diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeLargestCrossSections.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeLargestCrossSections.cpp index 156d7333a7..ffa4c89f98 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeLargestCrossSections.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeLargestCrossSections.cpp @@ -83,7 +83,7 @@ Result<> ComputeLargestCrossSections::operator()() stride3 = inPlane1 * inPlane2; } - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Computing Cross Section for {} planes", outPlane)); + m_MessageHandler.sendInfoMessage(fmt::format("Computing Cross Section for {} planes", outPlane)); for(size_t i = 0; i < outPlane; i++) { diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeMomentInvariants2D.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeMomentInvariants2D.cpp index 82a62b1aa5..259307473e 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeMomentInvariants2D.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeMomentInvariants2D.cpp @@ -187,7 +187,7 @@ class ComputeMomentInvariants2DImpl { m_Omega1[featureId] = 0.0f; m_Omega2[featureId] = 0.0f; - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("[{}/{}] : Feature {} is NOT strictly 2D in the XY plane. Skipping this feature.", featureId, numFeatures, featureId)); + m_MessageHandler.sendInfoMessage(fmt::format("[{}/{}] : Feature {} is NOT strictly 2D in the XY plane. Skipping this feature.", featureId, numFeatures, featureId)); return; } @@ -236,7 +236,7 @@ class ComputeMomentInvariants2DImpl centralMoments[static_cast(featureId) * 9UL + comp] = static_cast(m2DInternal[comp]); } - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("[{}/{}] : Completed", featureId, numFeatures)); + m_MessageHandler.sendInfoMessage(fmt::format("[{}/{}] : Completed", featureId, numFeatures)); if(m_ShouldCancel) { @@ -265,7 +265,7 @@ class ComputeMomentInvariants2DImpl { m_Omega1[featureId] = 0.0f; m_Omega2[featureId] = 0.0f; - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("[{}/{}] : Feature {} is NOT strictly 2D in the XY plane. Skipping this feature.", featureId, numFeatures, featureId)); + m_MessageHandler.sendInfoMessage(fmt::format("[{}/{}] : Feature {} is NOT strictly 2D in the XY plane. Skipping this feature.", featureId, numFeatures, featureId)); return; } @@ -308,7 +308,7 @@ class ComputeMomentInvariants2DImpl m_Omega1[featureId] = static_cast(omega1); m_Omega2[featureId] = static_cast(omega2); - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("[{}/{}] : Completed", featureId, numFeatures)); + m_MessageHandler.sendInfoMessage(fmt::format("[{}/{}] : Completed", featureId, numFeatures)); if(m_ShouldCancel) { diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeNeighborListStatistics.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeNeighborListStatistics.cpp index 1842305800..5c5cdf8f66 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeNeighborListStatistics.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeNeighborListStatistics.cpp @@ -3,8 +3,8 @@ #include "simplnx/Utilities/DataArrayUtilities.hpp" #include "simplnx/Utilities/FilterUtilities.hpp" #include "simplnx/Utilities/Math/StatisticsCalculations.hpp" -#include "simplnx/Utilities/MessageHelper.hpp" #include "simplnx/Utilities/ParallelDataAlgorithm.hpp" +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" using namespace nx::core; @@ -22,7 +22,7 @@ class ComputeNeighborListStatisticsImpl using StoreType = AbstractDataStore; ComputeNeighborListStatisticsImpl(ComputeNeighborListStatistics* filter, const INeighborList& source, bool length, bool min, bool max, bool mean, bool median, bool stdDeviation, bool summation, - std::vector& arrays, const std::atomic_bool& shouldCancel, ProgressMessageHelper& progressMessageHelper) + std::vector& arrays, const std::atomic_bool& shouldCancel) : m_Filter(filter) , m_ShouldCancel(shouldCancel) , m_Source(source) @@ -34,7 +34,6 @@ class ComputeNeighborListStatisticsImpl , m_StdDeviation(stdDeviation) , m_Summation(summation) , m_Arrays(arrays) - , m_ProgressMessageHelper(progressMessageHelper) { } @@ -80,7 +79,6 @@ class ComputeNeighborListStatisticsImpl const auto& sourceList = dynamic_cast(m_Source); - ProgressMessenger progressMessenger = m_ProgressMessageHelper.createProgressMessenger(); for(usize i = start; i < end; i++) { if(m_ShouldCancel) @@ -126,7 +124,7 @@ class ComputeNeighborListStatisticsImpl array6->setValue(i, val); } - progressMessenger.sendProgressMessage(1); + m_Filter->sendThreadSafeProgressMessage(1); } } @@ -149,7 +147,6 @@ class ComputeNeighborListStatisticsImpl bool m_Summation = false; std::vector& m_Arrays; - ProgressMessageHelper& m_ProgressMessageHelper; }; } // namespace @@ -160,12 +157,20 @@ ComputeNeighborListStatistics::ComputeNeighborListStatistics(DataStructure& data , m_InputValues(inputValues) , m_ShouldCancel(shouldCancel) , m_MessageHandler(msgHandler) +, m_Throttle(msgHandler) { } // ----------------------------------------------------------------------------- ComputeNeighborListStatistics::~ComputeNeighborListStatistics() noexcept = default; +// ----------------------------------------------------------------------------- +void ComputeNeighborListStatistics::sendThreadSafeProgressMessage(usize counter) +{ + std::lock_guard guard(m_ProgressMessage_Mutex); + m_Throttle.incrementPercent(counter); +} + // ----------------------------------------------------------------------------- Result<> ComputeNeighborListStatistics::operator()() { @@ -214,17 +219,14 @@ Result<> ComputeNeighborListStatistics::operator()() arrays[6] = m_DataStructure.getDataAs(m_InputValues->SummationPath); } - MessageHelper messageHelper(m_MessageHandler); - ProgressMessageHelper progresssMessageHelper = messageHelper.createProgressMessageHelper(); - progresssMessageHelper.setMaxProgresss(numTuples); - progresssMessageHelper.setProgressMessageTemplate("Finding Statistics || {:.2f}% Completed"); + m_Throttle.reset(numTuples, "Finding Statistics"); // Allow data-based parallelization ParallelDataAlgorithm dataAlg; dataAlg.setRange(0, numTuples); ExecuteParallelFunction(type, dataAlg, this, inputINeighborList, m_InputValues->FindLength, m_InputValues->FindMin, m_InputValues->FindMax, m_InputValues->FindMean, m_InputValues->FindMedian, m_InputValues->FindStdDeviation, m_InputValues->FindSummation, arrays, - m_ShouldCancel, progresssMessageHelper); + m_ShouldCancel); return {}; } diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeNeighborListStatistics.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeNeighborListStatistics.hpp index 4f65dbd8ff..e83195854e 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeNeighborListStatistics.hpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeNeighborListStatistics.hpp @@ -6,6 +6,7 @@ #include "simplnx/DataStructure/DataPath.hpp" #include "simplnx/DataStructure/DataStructure.hpp" #include "simplnx/Filter/IFilter.hpp" +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" #include #include @@ -52,11 +53,19 @@ class SIMPLNXCORE_EXPORT ComputeNeighborListStatistics const std::atomic_bool& getCancel(); + /** + * @brief Thread-safe progress update. Safe to call from ParallelDataAlgorithm workers. + * @param counter Items completed since the previous call + */ + void sendThreadSafeProgressMessage(usize counter); + private: DataStructure& m_DataStructure; const ComputeNeighborListStatisticsInputValues* m_InputValues = nullptr; const std::atomic_bool& m_ShouldCancel; const IFilter::MessageHandler& m_MessageHandler; + mutable std::mutex m_ProgressMessage_Mutex; + ThrottledMessageHandler m_Throttle; }; } // namespace nx::core diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeNeighborhoods.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeNeighborhoods.cpp index 19ef477bbd..6cb1874383 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeNeighborhoods.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeNeighborhoods.cpp @@ -9,8 +9,8 @@ #include "simplnx/DataStructure/DataStructure.hpp" #include "simplnx/DataStructure/Geometry/ImageGeom.hpp" #include "simplnx/Filter/IFilter.hpp" -#include "simplnx/Utilities/MessageHelper.hpp" #include "simplnx/Utilities/ParallelDataAlgorithm.hpp" +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" #include @@ -69,14 +69,13 @@ class ComputeNeighborhoodsImpl { public: ComputeNeighborhoodsImpl(ComputeNeighborhoods* filter, const nx::core::AbstractDataStore& centroids, const std::vector& bins, const std::vector& radii, float32 binSize, - const std::atomic_bool& shouldCancel, ProgressMessageHelper& progressMessageHelper) + const std::atomic_bool& shouldCancel) : m_Filter(filter) , m_Centroids(centroids) , m_Bins(bins) , m_Radii(radii) , m_BinSize(binSize) , m_ShouldCancel(shouldCancel) - , m_ProgressMessageHelper(progressMessageHelper) { } @@ -97,13 +96,12 @@ class ComputeNeighborhoodsImpl binToFeatures[key].push_back(i); } - ProgressMessenger progressMessenger = m_ProgressMessageHelper.createProgressMessenger(); for(usize i = start; i < end; i++) { incCount++; if(incCount >= increment) { - progressMessenger.sendProgressMessage(incCount, [&](usize currentProgress, usize maxProgress) { return fmt::format("{}/{}", currentProgress, maxProgress); }); + m_Filter->sendThreadSafeProgressMessage(incCount); incCount = 0; } @@ -170,7 +168,7 @@ class ComputeNeighborhoodsImpl } } } - progressMessenger.sendProgressMessage(incCount); + m_Filter->sendThreadSafeProgressMessage(incCount); } void operator()(const Range& range) const @@ -185,7 +183,6 @@ class ComputeNeighborhoodsImpl const std::vector& m_Radii; float32 m_BinSize; const std::atomic_bool& m_ShouldCancel; - ProgressMessageHelper& m_ProgressMessageHelper; }; } // namespace @@ -195,13 +192,20 @@ ComputeNeighborhoods::ComputeNeighborhoods(DataStructure& dataStructure, const I , m_InputValues(inputValues) , m_ShouldCancel(shouldCancel) , m_MessageHandler(mesgHandler) -, m_MessageHelper(m_MessageHandler) +, m_Throttle(mesgHandler) { } // ----------------------------------------------------------------------------- ComputeNeighborhoods::~ComputeNeighborhoods() noexcept = default; +// ----------------------------------------------------------------------------- +void ComputeNeighborhoods::sendThreadSafeProgressMessage(usize counter) +{ + std::lock_guard guard(m_ProgressMessage_Mutex); + m_Throttle.incrementCount(counter); +} + // ----------------------------------------------------------------------------- void ComputeNeighborhoods::updateNeighborHood(usize sourceIndex, usize destIndex) { @@ -225,9 +229,7 @@ Result<> ComputeNeighborhoods::operator()() return {}; } - ProgressMessageHelper progressMessageHelper = m_MessageHelper.createProgressMessageHelper(); - progressMessageHelper.setMaxProgresss(totalFeatures); - progressMessageHelper.setProgressMessageTemplate("Finding Feature Neighborhoods: {:.2f}%"); + m_Throttle.reset(totalFeatures, "Finding Feature Neighborhoods"); m_LocalNeighborhoodList.resize(totalFeatures); @@ -260,7 +262,7 @@ Result<> ComputeNeighborhoods::operator()() { avgDiameter /= static_cast(totalFeatures - 1); } - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Feature Average Diameter: '{}'", avgDiameter)); + m_MessageHandler.sendInfoMessage(fmt::format("Feature Average Diameter: '{}'", avgDiameter)); for(usize i = 1; i < totalFeatures; i++) { @@ -301,7 +303,7 @@ Result<> ComputeNeighborhoods::operator()() IParallelAlgorithm::AlgorithmStores algStores; algStores.push_back(¢roids); parallelAlgorithm.requireStoresInMemory(algStores); - parallelAlgorithm.execute(ComputeNeighborhoodsImpl(this, centroids, bins, radii, binSize, m_ShouldCancel, progressMessageHelper)); + parallelAlgorithm.execute(ComputeNeighborhoodsImpl(this, centroids, bins, radii, binSize, m_ShouldCancel)); // Output Variables auto& outputNeighborList = m_DataStructure.getDataRefAs>(m_InputValues->NeighborhoodListArrayName); diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeNeighborhoods.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeNeighborhoods.hpp index 23fc5012b8..f4b9f1a5bf 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeNeighborhoods.hpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeNeighborhoods.hpp @@ -7,7 +7,7 @@ #include "simplnx/DataStructure/DataStructure.hpp" #include "simplnx/DataStructure/NeighborList.hpp" #include "simplnx/Filter/IFilter.hpp" -#include "simplnx/Utilities/MessageHelper.hpp" +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" #include @@ -44,13 +44,20 @@ class SIMPLNXCORE_EXPORT ComputeNeighborhoods void updateNeighborHood(usize sourceIndex, usize targetIndex); + /** + * @brief Thread-safe progress update. Safe to call from ParallelDataAlgorithm workers. + * @param counter Items completed since the previous call + */ + void sendThreadSafeProgressMessage(usize counter); + private: DataStructure& m_DataStructure; const ComputeNeighborhoodsInputValues* m_InputValues = nullptr; const std::atomic_bool& m_ShouldCancel; const IFilter::MessageHandler& m_MessageHandler; std::mutex m_Mutex; - MessageHelper m_MessageHelper; + mutable std::mutex m_ProgressMessage_Mutex; + ThrottledMessageHandler m_Throttle; Int32Array* m_Neighborhoods = nullptr; std::vector> m_LocalNeighborhoodList; }; diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeSurfaceAreaToVolume.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeSurfaceAreaToVolume.cpp index 36518d9b30..b4e2ca300c 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeSurfaceAreaToVolume.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeSurfaceAreaToVolume.cpp @@ -73,7 +73,7 @@ Result<> ComputeSurfaceAreaToVolume::operator()() // Start looping over the regular grid data (This could be either an Image Geometry or a Rectilinear Grid geometry (in theory) for(int64 zIdx = 0; zIdx < zPoints; zIdx++) { - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Computing Z Slice: '{}'", zIdx)); + m_MessageHandler.sendInfoMessage(fmt::format("Computing Z Slice: '{}'", zIdx)); if(m_ShouldCancel) { return {}; @@ -154,7 +154,7 @@ Result<> ComputeSurfaceAreaToVolume::operator()() if(m_InputValues->CalculateSphericity) // Calc the sphericity if requested { - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Computing Sphericity")); + m_MessageHandler.sendInfoMessage(fmt::format("Computing Sphericity")); auto& sphericity = m_DataStructure.getDataAs(m_InputValues->SphericityArrayName)->getDataStoreRef(); for(usize i = 1; i < static_cast(numFeatures); i++) diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeTriangleGeomCentroids.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeTriangleGeomCentroids.cpp index ec08855a0d..3036691292 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeTriangleGeomCentroids.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeTriangleGeomCentroids.cpp @@ -47,14 +47,14 @@ Result<> ComputeTriangleGeomCentroids::operator()() auto& featAttrMat = m_DataStructure.getDataRefAs(m_InputValues->FeatureAttributeMatrixPath); if(featAttrMat.getNumberOfTuples() < *maxFeatureId + 1) { - m_MessageHandler(IFilter::Message::Type::Info, "Increasing Number of tuples in target feature attribute matrix..."); + m_MessageHandler.sendInfoMessage("Increasing Number of tuples in target feature attribute matrix..."); featAttrMat.resizeTuples(ShapeType{static_cast(*maxFeatureId + 1)}); } MeshIndexType numFeatures = featAttrMat.getNumberOfTuples(); auto& centroids = m_DataStructure.getDataAs(m_InputValues->CentroidsArrayPath)->getDataStoreRef(); std::vector> vertexSets(numFeatures); - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Gathering unique vertices for {} triangles", numTriangles)); + m_MessageHandler.sendInfoMessage(fmt::format("Gathering unique vertices for {} triangles", numTriangles)); for(MeshIndexType i = 0; i < numTriangles; i++) { @@ -74,7 +74,7 @@ Result<> ComputeTriangleGeomCentroids::operator()() } } - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Computing centroids for {} features", numFeatures)); + m_MessageHandler.sendInfoMessage(fmt::format("Computing centroids for {} features", numFeatures)); for(MeshIndexType i = 0; i < numFeatures; i++) { diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeVertexToTriangleDistances.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeVertexToTriangleDistances.cpp index 7efd4043ba..c00110c60d 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeVertexToTriangleDistances.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeVertexToTriangleDistances.cpp @@ -5,9 +5,9 @@ #include "simplnx/DataStructure/DataGroup.hpp" #include "simplnx/DataStructure/Geometry/TriangleGeom.hpp" #include "simplnx/DataStructure/Geometry/VertexGeom.hpp" -#include "simplnx/Utilities/MessageHelper.hpp" #include "simplnx/Utilities/ParallelDataAlgorithm.hpp" #include "simplnx/Utilities/RTree.hpp" +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" using namespace nx::core; @@ -109,8 +109,7 @@ class ComputeVertexToTriangleDistancesImpl { public: ComputeVertexToTriangleDistancesImpl(ComputeVertexToTriangleDistances* filter, const SharedTriListT& triangles, const SharedVertexListT& vertices, SharedVertexListT& sourcePoints, - Float32AbstractDataStore& distances, Int64AbstractDataStore& closestTri, const Float64AbstractDataStore& normals, const RTreeType rtree, - ProgressMessageHelper& progressMessageHelper) + Float32AbstractDataStore& distances, Int64AbstractDataStore& closestTri, const Float64AbstractDataStore& normals, const RTreeType rtree) : m_Filter(filter) , m_SharedTriangleList(triangles) , m_TriangleVertices(vertices) @@ -119,7 +118,6 @@ class ComputeVertexToTriangleDistancesImpl , m_ClosestTri(closestTri) , m_Normals(normals) , m_RTree(rtree) - , m_ProgressMessageHelper(progressMessageHelper) { } virtual ~ComputeVertexToTriangleDistancesImpl() = default; @@ -131,7 +129,6 @@ class ComputeVertexToTriangleDistancesImpl void compute(usize start, usize end) const { - ProgressMessenger progressMessenger = m_ProgressMessageHelper.createProgressMessenger(); int64 counter = 0; auto progIncrement = static_cast((end - start) / 100); @@ -214,12 +211,12 @@ class ComputeVertexToTriangleDistancesImpl if(counter > progIncrement) { - progressMessenger.sendProgressMessage(counter); + m_Filter->sendThreadSafeProgressMessage(counter); counter = 0; } counter++; } - progressMessenger.sendProgressMessage(counter); + m_Filter->sendThreadSafeProgressMessage(counter); } private: @@ -231,7 +228,6 @@ class ComputeVertexToTriangleDistancesImpl Int64AbstractDataStore& m_ClosestTri; const Float64AbstractDataStore& m_Normals; const RTreeType m_RTree; - ProgressMessageHelper& m_ProgressMessageHelper; }; void GetBoundingBoxAtTri(const SharedTriListT& triList, const SharedVertexListT& vertList, size_t triId, nonstd::span bounds) @@ -259,6 +255,7 @@ ComputeVertexToTriangleDistances::ComputeVertexToTriangleDistances(DataStructure , m_InputValues(inputValues) , m_ShouldCancel(shouldCancel) , m_MessageHandler(mesgHandler) +, m_Throttle(mesgHandler) { } @@ -271,6 +268,13 @@ const std::atomic_bool& ComputeVertexToTriangleDistances::getCancel() return m_ShouldCancel; } +// ----------------------------------------------------------------------------- +void ComputeVertexToTriangleDistances::sendThreadSafeProgressMessage(usize counter) +{ + std::lock_guard guard(m_ProgressMessage_Mutex); + m_Throttle.incrementPercent(counter); +} + // ----------------------------------------------------------------------------- Result<> ComputeVertexToTriangleDistances::operator()() { @@ -298,16 +302,13 @@ Result<> ComputeVertexToTriangleDistances::operator()() auto& closestTriangleIdsArray = m_DataStructure.getDataAs(m_InputValues->ClosestTriangleIdArrayPath)->getDataStoreRef(); closestTriangleIdsArray.fill(-1); // -1 means it never found the closest triangle? - MessageHelper messageHelper(m_MessageHandler); - ProgressMessageHelper progressMessageHelper = messageHelper.createProgressMessageHelper(); - progressMessageHelper.setMaxProgresss(totalElements); - progressMessageHelper.setProgressMessageTemplate("Finding Distances || {:.2f}% Completed"); + m_Throttle.reset(totalElements, "Finding Vertex To Triangle Distances"); // Allow data-based parallelization ParallelDataAlgorithm dataAlg; dataAlg.setParallelizationEnabled(true); dataAlg.setRange(0, totalElements); - dataAlg.execute(ComputeVertexToTriangleDistancesImpl(this, triangles, vertices, sourceVertices, distancesArray, closestTriangleIdsArray, normalsArray, m_RTree, progressMessageHelper)); + dataAlg.execute(ComputeVertexToTriangleDistancesImpl(this, triangles, vertices, sourceVertices, distancesArray, closestTriangleIdsArray, normalsArray, m_RTree)); return {}; } diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeVertexToTriangleDistances.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeVertexToTriangleDistances.hpp index 0207b450a5..4e024d55b1 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeVertexToTriangleDistances.hpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ComputeVertexToTriangleDistances.hpp @@ -5,9 +5,12 @@ #include "simplnx/DataStructure/DataPath.hpp" #include "simplnx/DataStructure/DataStructure.hpp" #include "simplnx/Filter/IFilter.hpp" +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" + #include "simplnx/Parameters/ArrayCreationParameter.hpp" #include "simplnx/Parameters/ArraySelectionParameter.hpp" #include "simplnx/Parameters/DataGroupSelectionParameter.hpp" +#include namespace nx::core { @@ -41,11 +44,19 @@ class SIMPLNXCORE_EXPORT ComputeVertexToTriangleDistances const std::atomic_bool& getCancel(); + /** + * @brief Thread-safe progress update. Safe to call from ParallelDataAlgorithm workers. + * @param counter Items completed since the previous call + */ + void sendThreadSafeProgressMessage(usize counter); + private: DataStructure& m_DataStructure; const ComputeVertexToTriangleDistancesInputValues* m_InputValues = nullptr; const std::atomic_bool& m_ShouldCancel; const IFilter::MessageHandler& m_MessageHandler; + mutable std::mutex m_ProgressMessage_Mutex; + ThrottledMessageHandler m_Throttle; }; } // namespace nx::core diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ConcatenateDataArrays.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ConcatenateDataArrays.hpp index 421e2f4272..b716f7843d 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ConcatenateDataArrays.hpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ConcatenateDataArrays.hpp @@ -41,7 +41,7 @@ typename std::enable_if::value, Result<>>::type return {}; } - messageHandler({IFilter::Message::Type::Info, fmt::format("Concatenating array '{}'...", inputArrayPath.toString())}); + messageHandler.sendInfoMessage(fmt::format("Concatenating array '{}'...", inputArrayPath.toString())); const auto& inputDataArray = dataStructure.getDataRefAs(inputArrayPath); auto result = CopyFromArray::CopyData(inputDataArray, outputDataArray, destTupleOffset, 0, inputDataArray.getNumberOfTuples()); if(result.invalid()) @@ -67,7 +67,7 @@ Result<> ConcatenateNeighborListsImpl(DataStructure& dataStructure, const std::v return {}; } - messageHandler({IFilter::Message::Type::Info, fmt::format("Concatenating neighbor list '{}'...", inputNeighborListPath.toString())}); + messageHandler.sendInfoMessage(fmt::format("Concatenating neighbor list '{}'...", inputNeighborListPath.toString())); const auto& inputNeighborList = dataStructure.getDataRefAs>(inputNeighborListPath); for(int32 listIdx = 0; listIdx < inputNeighborList.getNumberOfLists(); ++listIdx) { diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ConvertColorToGrayScale.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ConvertColorToGrayScale.cpp index 8a4e9f4267..d00d947212 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ConvertColorToGrayScale.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ConvertColorToGrayScale.cpp @@ -191,7 +191,7 @@ Result<> ConvertColorToGrayScale::operator()() auto outputPathIter = m_InputValues->OutputDataArrayPaths.begin(); for(const auto& arrayPath : m_InputValues->InputDataArrayPaths) { - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Converting data '{}'", arrayPath.toString())); + m_MessageHandler.sendInfoMessage(fmt::format("Converting data '{}'", arrayPath.toString())); if(getCancel()) { diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/CopyFeatureArrayToElementArray.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/CopyFeatureArrayToElementArray.cpp index e49bfad2e5..a9b5b6afe1 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/CopyFeatureArrayToElementArray.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/CopyFeatureArrayToElementArray.cpp @@ -127,7 +127,7 @@ Result<> CopyFeatureArrayToElementArray::operator()() const auto& selectedFeatureArray = m_DataStructure.getDataRefAs(selectedFeatureArrayPath); auto& createdArray = m_DataStructure.getDataRefAs(createdArrayPath); - m_MessageHandler(IFilter::ProgressMessage{IFilter::ProgressMessage::Type::Info, fmt::format("Copying data into target array '{}'...", createdArrayPath.toString())}); + m_MessageHandler.sendInfoMessage(fmt::format("Copying data into target array '{}'...", createdArrayPath.toString())); ParallelDataAlgorithm dataAlg; dataAlg.setRange(0, featureIds.getNumberOfTuples()); dataAlg.requireArraysInMemory({&featureIds, &selectedFeatureArray, &createdArray}); diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/CropEdgeGeometry.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/CropEdgeGeometry.cpp index 7c0388ba3b..46d2ec4b01 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/CropEdgeGeometry.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/CropEdgeGeometry.cpp @@ -354,7 +354,7 @@ Result<> CropEdgeGeometry::operator()() auto& newDataArray = dynamic_cast(destVertexAttrMatrix.at(srcName)); - m_MessageHandler(fmt::format("Cropping Volume || Copying Vertex Array {}", srcName)); + m_MessageHandler.sendInfoMessage(fmt::format("Cropping Volume || Copying Vertex Array {}", srcName)); ExecuteParallelFunction(oldDataArray.getDataType(), taskRunner, oldDataArray, newDataArray, srcVertexAttrMatrix, vertexReferenced, m_ShouldCancel); } taskRunner.wait(); // This will spill over if the number of DataArrays to process does not divide evenly by the number of threads. @@ -396,7 +396,7 @@ Result<> CropEdgeGeometry::operator()() auto& newDataArray = dynamic_cast(destEdgesAttrMatrix.at(srcName)); - m_MessageHandler(fmt::format("Cropping Volume || Copying Edge Array {}", srcName)); + m_MessageHandler.sendInfoMessage(fmt::format("Cropping Volume || Copying Edge Array {}", srcName)); ExecuteParallelFunction(oldDataArray.getDataType(), taskRunner, oldDataArray, newDataArray, srcEdgesAttrMatrix, edgesMask, m_ShouldCancel); } taskRunner.wait(); diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/CropImageGeometry.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/CropImageGeometry.cpp index 6825b65cda..be02f9398c 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/CropImageGeometry.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/CropImageGeometry.cpp @@ -182,7 +182,7 @@ Result<> CropImageGeometry::operator()() auto& newDataArray = dynamic_cast(destCellDataAM.at(srcName)); - m_MessageHandler(fmt::format("Cropping Volume || Copying Data Array {}", srcName)); + m_MessageHandler.sendInfoMessage(fmt::format("Cropping Volume || Copying Data Array {}", srcName)); ExecuteParallelFunction(oldDataArray.getDataType(), taskRunner, oldDataArray, newDataArray, srcImageGeom, bounds, m_ShouldCancel); } taskRunner.wait(); // This will spill over if the number of DataArrays to process does not divide evenly by the number of threads. diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/DBSCAN.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/DBSCAN.cpp index 342cab6501..47deae2a20 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/DBSCAN.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/DBSCAN.cpp @@ -6,7 +6,7 @@ #include "simplnx/Utilities/ClusteringUtilities.hpp" #include "simplnx/Utilities/FilterUtilities.hpp" #include "simplnx/Utilities/MaskCompareUtilities.hpp" -#include "simplnx/Utilities/MessageHelper.hpp" +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" #include @@ -106,13 +106,13 @@ class HyperGridBitMap3D : public HyperGridBitMap HyperGridBitMap3D() = delete; template - HyperGridBitMap3D(const std::atomic_bool& shouldCancel, MessageHelper& messageHelper, const AbstractDataStore& inputArray, float32 epsilon, + HyperGridBitMap3D(const std::atomic_bool& shouldCancel, const IFilter::MessageHandler& messageHandler, const AbstractDataStore& inputArray, float32 epsilon, const std::unique_ptr& mask) : HyperGridBitMap() { - ThrottledMessenger throttledMessenger = messageHelper.createThrottledMessenger(); + ThrottledMessageHandler throttle(messageHandler); - messageHelper.sendMessage(" - Determining bounds..."); + messageHandler.sendInfoMessage(" - Determining bounds..."); // Load array bounds std::array bounds = {std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN()}; @@ -123,7 +123,7 @@ class HyperGridBitMap3D : public HyperGridBitMap return; } - throttledMessenger.sendThrottledMessage([&]() { return fmt::format(" - Finding Bounds || {:.2f}% Complete", CalculatePercentComplete(i, inputArray.getNumberOfTuples())); }); + throttle.updatePercent(" - Finding Bounds", i, inputArray.getNumberOfTuples()); if(!mask->isTrue(i)) { @@ -158,7 +158,7 @@ class HyperGridBitMap3D : public HyperGridBitMap dims[1] = static_cast(((bounds[4] + buffer) - origin[1]) / spacing[1]) + 2; dims[2] = static_cast(((bounds[5] + buffer) - origin[2]) / spacing[2]) + 2; - messageHelper.sendMessage(" - Binning values into a regular grid..."); + messageHandler.sendInfoMessage(" - Binning values into a regular grid..."); // Fill the BitMap { std::vector> positions = {}; @@ -174,7 +174,7 @@ class HyperGridBitMap3D : public HyperGridBitMap return; } - throttledMessenger.sendThrottledMessage([&]() { return fmt::format(" - Binning || {:.2f}% Complete", CalculatePercentComplete(tup, numTup * 2)); }); + throttle.updatePercent(" - Binning", tup, numTup * 2); if(!mask->isTrue(tup)) { @@ -191,7 +191,7 @@ class HyperGridBitMap3D : public HyperGridBitMap grids[bin] = true; } - messageHelper.sendMessage(" - Compressing regular grid..."); + messageHandler.sendInfoMessage(" - Compressing regular grid..."); usize zSize = dims[1] * dims[0]; usize ySize = dims[0]; usize activeGridCount = 0; @@ -221,7 +221,7 @@ class HyperGridBitMap3D : public HyperGridBitMap return; } - throttledMessenger.sendThrottledMessage([&]() { return fmt::format(" - Binning || {:.2f}% Complete", CalculatePercentComplete(numTup + tup, numTup * 2)); }); + throttle.updatePercent(" - Binning", numTup + tup, numTup * 2); if(!mask->isTrue(tup)) { @@ -245,7 +245,7 @@ class HyperGridBitMap3D : public HyperGridBitMap grid.shrink_to_fit(); } - messageHelper.sendMessage(" - Generating adjacency matrix for search..."); + messageHandler.sendInfoMessage(" - Generating adjacency matrix for search..."); /** * This could be modified to 3 passes on the positions vector with custom predicates and ths std::sort function, * but we are sacrificing space for speed, because its a subset of a known predefined grid @@ -318,11 +318,11 @@ class HyperGridBitMap2D : public HyperGridBitMap HyperGridBitMap2D() = delete; template - HyperGridBitMap2D(const std::atomic_bool& shouldCancel, MessageHelper& messageHelper, const AbstractDataStore& inputArray, float32 epsilon, + HyperGridBitMap2D(const std::atomic_bool& shouldCancel, const IFilter::MessageHandler& messageHandler, const AbstractDataStore& inputArray, float32 epsilon, const std::unique_ptr& mask) : HyperGridBitMap() { - ThrottledMessenger throttledMessenger = messageHelper.createThrottledMessenger(); + ThrottledMessageHandler throttle(messageHandler); // Load array bounds std::array bounds = {std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN(), @@ -334,7 +334,7 @@ class HyperGridBitMap2D : public HyperGridBitMap return; } - throttledMessenger.sendThrottledMessage([&]() { return fmt::format(" - Finding Bounds || {:.2f}% Complete", CalculatePercentComplete(i, inputArray.getNumberOfTuples())); }); + throttle.updatePercent(" - Finding Bounds", i, inputArray.getNumberOfTuples()); if(!mask->isTrue(i)) { @@ -365,7 +365,7 @@ class HyperGridBitMap2D : public HyperGridBitMap dims[0] = static_cast(((bounds[2] + buffer) - origin[0]) / spacing[0]) + 2; dims[1] = static_cast(((bounds[3] + buffer) - origin[1]) / spacing[1]) + 2; - messageHelper.sendMessage(" - Binning values into a regular grid..."); + messageHandler.sendInfoMessage(" - Binning values into a regular grid..."); // Fill the BitMap { std::vector> positions = {}; @@ -381,7 +381,7 @@ class HyperGridBitMap2D : public HyperGridBitMap return; } - throttledMessenger.sendThrottledMessage([&]() { return fmt::format(" - Binning || {:.2f}% Complete", CalculatePercentComplete(tup, numTup * 2)); }); + throttle.updatePercent(" - Binning", tup, numTup * 2); if(!mask->isTrue(tup)) { @@ -398,7 +398,7 @@ class HyperGridBitMap2D : public HyperGridBitMap grids[bin] = true; } - messageHelper.sendMessage(" - Compressing regular grid..."); + messageHandler.sendInfoMessage(" - Compressing regular grid..."); usize ySize = dims[0]; usize activeGridCount = 0; @@ -426,7 +426,7 @@ class HyperGridBitMap2D : public HyperGridBitMap return; } - throttledMessenger.sendThrottledMessage([&]() { return fmt::format(" - Binning || {:.2f}% Complete", CalculatePercentComplete(numTup + tup, numTup * 2)); }); + throttle.updatePercent(" - Binning", numTup + tup, numTup * 2); if(!mask->isTrue(tup)) { @@ -449,7 +449,7 @@ class HyperGridBitMap2D : public HyperGridBitMap grid.shrink_to_fit(); } - messageHelper.sendMessage(" - Generating adjacency matrix for search..."); + messageHandler.sendInfoMessage(" - Generating adjacency matrix for search..."); /** * This could be modified to 2 passes on the positions vector with custom predicates and ths std::sort function, * but we are sacrificing space for speed, because its a subset of a known predefined grid @@ -705,20 +705,20 @@ class GDCF { public: GDCF() = delete; - GDCF(const std::atomic_bool& shouldCancel, MessageHelper& messageHelper, const AbstractDataStore& inputArray, float32 epsilon, const std::unique_ptr& mask, - ClusterUtilities::DistanceMetric distMetric) - : hyperGridBitMap(HGBPT(shouldCancel, messageHelper, inputArray, epsilon, mask)) + GDCF(const std::atomic_bool& shouldCancel, const IFilter::MessageHandler& messageHandler, const AbstractDataStore& inputArray, float32 epsilon, + const std::unique_ptr& mask, ClusterUtilities::DistanceMetric distMetric) + : hyperGridBitMap(HGBPT(shouldCancel, messageHandler, inputArray, epsilon, mask)) , m_Epsilon(epsilon) , m_InputDataStore(inputArray) , m_DistMetric(distMetric) , m_ShouldCancel(shouldCancel) - , m_MessageHelper(messageHelper) + , m_MessageHandler(messageHandler) { } Result<> cluster(usize minPoints, DBSCAN::ParseOrder parseOrder, std::mt19937_64::result_type seed = std::mt19937_64::default_seed) { - m_MessageHelper.sendMessage(" - Identifying core grids..."); + m_MessageHandler.sendInfoMessage(" - Identifying core grids..."); // Identify Core Grids std::vector coreGridIds = {}; for(usize i = 0; i < hyperGridBitMap.gridVoxels.size(); i++) @@ -739,7 +739,7 @@ class GDCF } // Sort Grids to reduce bias - m_MessageHelper.sendMessage(" - Sorting grids according to supplied parse order..."); + m_MessageHandler.sendInfoMessage(" - Sorting grids according to supplied parse order..."); switch(parseOrder) { case DBSCAN::ParseOrder::LowDensityFirst: { @@ -785,8 +785,8 @@ class GDCF return {}; } - m_MessageHelper.sendMessage("Identifying Qualifying Independent Clusters:"); - ThrottledMessenger throttledMessenger = m_MessageHelper.createThrottledMessenger(); + m_MessageHandler.sendInfoMessage("Identifying Qualifying Independent Clusters:"); + ThrottledMessageHandler throttle(m_MessageHandler); clusterForest.initialize(hyperGridBitMap.gridVoxels.size()); for(usize i = 0; i < coreGridIds.size(); i++) { @@ -795,7 +795,7 @@ class GDCF return {}; } - throttledMessenger.sendThrottledMessage([&]() { return fmt::format(" - Identifying clusters || {:.2f}% Complete", CalculatePercentComplete(i, coreGridIds.size())); }); + throttle.updatePercent(" - Identifying clusters", i, coreGridIds.size()); std::vector neighborGrids = NeighborGridQuery(coreGridIds[i], hyperGridBitMap); @@ -835,16 +835,16 @@ class GDCF } // Now determine if non-core grids are close enough to a cluster to be border else noise - m_MessageHelper.sendMessage("Expanding and Merging Applicable Clusters:"); + m_MessageHandler.sendInfoMessage("Expanding and Merging Applicable Clusters:"); usize loop = 1; usize operations = 0; do { - m_MessageHelper.sendMessage(fmt::format(" - Beginning cluster expansion pass: {}...", loop++)); + m_MessageHandler.sendInfoMessage(fmt::format(" - Beginning cluster expansion pass: {}...", loop++)); operations = 0; for(usize i = 0; i < hyperGridBitMap.gridVoxels.size(); i++) { - throttledMessenger.sendThrottledMessage([&]() { return fmt::format(" - Expanding clusters || {:.2f}% Complete", CalculatePercentComplete(i, hyperGridBitMap.gridVoxels.size())); }); + throttle.updatePercent(" - Expanding clusters", i, hyperGridBitMap.gridVoxels.size()); if(m_ShouldCancel) { return {}; @@ -902,7 +902,7 @@ class GDCF } } while(operations > 0); - m_MessageHelper.sendMessage(" - Cleaning up cluster identifiers..."); + m_MessageHandler.sendInfoMessage(" - Cleaning up cluster identifiers..."); // clean up cluster forest std::vector clusters = {}; for(usize i = 0; i < clusterForest.clusterForestNodes.size(); i++) @@ -937,7 +937,7 @@ class GDCF return MakeWarningVoidResult(-85640, "No clusters detected - Consider reducing number of required points (`Minimum Points`) or increasing acceptable distance (`Epsilon`)."); } - ThrottledMessenger throttledMessenger = m_MessageHelper.createThrottledMessenger(); + ThrottledMessageHandler throttle(m_MessageHandler); // label fIdsDataStore.fill(0); for(usize gridIdx = 0; gridIdx < hyperGridBitMap.gridVoxels.size(); gridIdx++) @@ -947,7 +947,7 @@ class GDCF return {}; } - throttledMessenger.sendThrottledMessage([&]() { return fmt::format(" - Labeling || {:.2f}% Complete", CalculatePercentComplete(gridIdx, hyperGridBitMap.gridVoxels.size())); }); + throttle.updatePercent(" - Labeling", gridIdx, hyperGridBitMap.gridVoxels.size()); int32 featureId = clusterForest.clusterForestNodes[clusterForest.findClusterRoot(gridIdx)].clusterId; for(usize pointIdx : hyperGridBitMap.gridVoxels[gridIdx]) @@ -968,7 +968,7 @@ class GDCF const AbstractDataStore& m_InputDataStore; ClusterUtilities::DistanceMetric m_DistMetric; const std::atomic_bool& m_ShouldCancel; - MessageHelper& m_MessageHelper; + const IFilter::MessageHandler& m_MessageHandler; // Uses Hoare's method for speed usize ProcessSection(std::vector& sorted, usize begin, usize end) const @@ -1035,17 +1035,17 @@ class GDCF template Result<> RunAlgorithm(const DBSCANInputValues* inputValues, const AbstractDataStore& inputArray, const std::unique_ptr& mask, Int32Array& featureIds, - MessageHelper& messageHelper, const std::atomic_bool& shouldCancel) + const IFilter::MessageHandler& messageHandler, const std::atomic_bool& shouldCancel) { - messageHelper.sendMessage("Partitioning Input Data:"); - AlgorithmT algorithm = AlgorithmT(shouldCancel, messageHelper, inputArray, inputValues->Epsilon, mask, inputValues->DistanceMetric); + messageHandler.sendInfoMessage("Partitioning Input Data:"); + AlgorithmT algorithm = AlgorithmT(shouldCancel, messageHandler, inputArray, inputValues->Epsilon, mask, inputValues->DistanceMetric); if(shouldCancel) { return {}; } - messageHelper.sendMessage("Clustering:"); + messageHandler.sendInfoMessage("Clustering:"); Result<> result = algorithm.cluster(inputValues->MinPoints, static_cast(inputValues->ParseOrder), inputValues->Seed); if(result.invalid() || !result.warnings().empty()) { @@ -1059,7 +1059,7 @@ Result<> RunAlgorithm(const DBSCANInputValues* inputValues, const AbstractDataSt return {}; } - messageHelper.sendMessage("Labeling:"); + messageHandler.sendInfoMessage("Labeling:"); return algorithm.label(featureIds.getDataStoreRef()); } @@ -1067,16 +1067,16 @@ struct DBSCANFunctor { template Result<> operator()(const DBSCANInputValues* inputValues, const IDataArray& clusterArray, const std::unique_ptr& mask, Int32Array& featureIds, - MessageHelper& messageHelper, const std::atomic_bool& shouldCancel) + const IFilter::MessageHandler& messageHandler, const std::atomic_bool& shouldCancel) { const auto& inputArray = dynamic_cast&>(clusterArray).getDataStoreRef(); if(inputArray.getNumberOfComponents() == 2) { - return RunAlgorithm, T>(inputValues, inputArray, mask, featureIds, messageHelper, shouldCancel); + return RunAlgorithm, T>(inputValues, inputArray, mask, featureIds, messageHandler, shouldCancel); } else if(inputArray.getNumberOfComponents() == 3) { - return RunAlgorithm, T>(inputValues, inputArray, mask, featureIds, messageHelper, shouldCancel); + return RunAlgorithm, T>(inputValues, inputArray, mask, featureIds, messageHandler, shouldCancel); } else { @@ -1103,7 +1103,6 @@ DBSCAN::~DBSCAN() noexcept = default; // ----------------------------------------------------------------------------- Result<> DBSCAN::operator()() { - MessageHelper messageHelper(m_MessageHandler); auto& clusteringArray = m_DataStructure.getDataRefAs(m_InputValues->ClusteringArrayPath); auto& featureIds = m_DataStructure.getDataRefAs(m_InputValues->FeatureIdsArrayPath); @@ -1120,7 +1119,7 @@ Result<> DBSCAN::operator()() return MakeErrorResult(-54060, message); } - Result<> result = ExecuteDataFunction(DBSCANFunctor{}, clusteringArray.getDataType(), m_InputValues, clusteringArray, maskCompare, featureIds, messageHelper, m_ShouldCancel); + Result<> result = ExecuteDataFunction(DBSCANFunctor{}, clusteringArray.getDataType(), m_InputValues, clusteringArray, maskCompare, featureIds, m_MessageHandler, m_ShouldCancel); if(result.invalid()) { return result; @@ -1131,7 +1130,7 @@ Result<> DBSCAN::operator()() return {}; } - messageHelper.sendMessage("Resizing clustering Attribute Matrix:"); + m_MessageHandler.sendInfoMessage("Resizing clustering Attribute Matrix:"); auto& featureIdsDataStore = featureIds.getDataStoreRef(); int32 maxCluster = *std::max_element(featureIdsDataStore.begin(), featureIdsDataStore.end()); m_DataStructure.getDataAs(m_InputValues->FeatureAM)->resizeTuples(ShapeType{static_cast(maxCluster + 1)}); diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ErodeDilateBadData.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ErodeDilateBadData.cpp index 0cc0ed0cd0..6dbca6684e 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ErodeDilateBadData.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ErodeDilateBadData.cpp @@ -3,9 +3,9 @@ #include "simplnx/DataStructure/DataArray.hpp" #include "simplnx/DataStructure/Geometry/ImageGeom.hpp" #include "simplnx/Utilities/DataGroupUtilities.hpp" -#include "simplnx/Utilities/MessageHelper.hpp" #include "simplnx/Utilities/NeighborUtilities.hpp" #include "simplnx/Utilities/ParallelTaskAlgorithm.hpp" +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" using namespace nx::core; namespace @@ -17,14 +17,13 @@ class ErodeDilateBadDataTransferDataImpl ErodeDilateBadDataTransferDataImpl(const ErodeDilateBadDataTransferDataImpl&) = default; ErodeDilateBadDataTransferDataImpl(ErodeDilateBadData* filterAlg, usize totalPoints, ChoicesParameter::ValueType operation, const Int32AbstractDataStore& featureIds, - const std::vector& neighbors, const std::shared_ptr& dataArrayPtr, MessageHelper& messageHelper) + const std::vector& neighbors, const std::shared_ptr& dataArrayPtr) : m_FilterAlg(filterAlg) , m_TotalPoints(totalPoints) , m_Operation(operation) , m_Neighbors(neighbors) , m_DataArrayPtr(dataArrayPtr) , m_FeatureIds(featureIds) - , m_MessageHelper(messageHelper) { } ErodeDilateBadDataTransferDataImpl(ErodeDilateBadDataTransferDataImpl&&) = default; // Move Constructor Not Implemented @@ -35,11 +34,16 @@ class ErodeDilateBadDataTransferDataImpl void operator()() const { - ThrottledMessenger throttledMessenger = m_MessageHelper.createThrottledMessenger(); - std::string arrayName = m_DataArrayPtr->getName(); + const std::string arrayName = m_DataArrayPtr->getName(); + // Pre-gate on a count so the shared progress mutex is taken about a hundred times per array + // rather than once per voxel. Matches RequireMinimumSizeFeatures. + const usize progressIncrement = std::max(m_TotalPoints / 100ULL, 1ULL); for(usize i = 0; i < m_TotalPoints; i++) { - throttledMessenger.sendThrottledMessage([&]() { return fmt::format("Processing {}: {:.2f}% completed", arrayName, CalculatePercentComplete(i, m_TotalPoints)); }); + if(i % progressIncrement == 0) + { + m_FilterAlg->sendThreadSafeProgressMessage(fmt::format("Processing {}: {:.2f}% completed", arrayName, CalculatePercentComplete(i, m_TotalPoints))); + } const int32 featureName = m_FeatureIds[i]; const int64 neighbor = m_Neighbors[i]; @@ -60,7 +64,6 @@ class ErodeDilateBadDataTransferDataImpl std::vector m_Neighbors; const std::shared_ptr m_DataArrayPtr; const Int32AbstractDataStore& m_FeatureIds; - MessageHelper& m_MessageHelper; }; } // namespace @@ -70,6 +73,7 @@ ErodeDilateBadData::ErodeDilateBadData(DataStructure& dataStructure, const IFilt , m_InputValues(inputValues) , m_ShouldCancel(shouldCancel) , m_MessageHandler(mesgHandler) +, m_Throttle(mesgHandler) { } @@ -82,6 +86,13 @@ const std::atomic_bool& ErodeDilateBadData::getCancel() return m_ShouldCancel; } +// ----------------------------------------------------------------------------- +void ErodeDilateBadData::sendThreadSafeProgressMessage(const std::string& message) +{ + std::lock_guard guard(m_ProgressMessage_Mutex); + m_Throttle.trySendMessage(message); +} + // ----------------------------------------------------------------------------- Result<> ErodeDilateBadData::operator()() { @@ -180,8 +191,6 @@ Result<> ErodeDilateBadData::operator()() // Build up a list of the DataArrays that we are going to operate on. const std::vector> voxelArrays = nx::core::GenerateDataArrayList(m_DataStructure, m_InputValues->FeatureIdsArrayPath, m_InputValues->IgnoredDataArrayPaths); - MessageHelper messageHelper(m_MessageHandler); - ParallelTaskAlgorithm taskRunner; taskRunner.setParallelizationEnabled(true); for(const auto& voxelArray : voxelArrays) @@ -193,14 +202,14 @@ Result<> ErodeDilateBadData::operator()() continue; } - taskRunner.execute(ErodeDilateBadDataTransferDataImpl(this, totalPoints, m_InputValues->Operation, featureIds, neighbors, voxelArray, messageHelper)); + taskRunner.execute(ErodeDilateBadDataTransferDataImpl(this, totalPoints, m_InputValues->Operation, featureIds, neighbors, voxelArray)); } taskRunner.wait(); // This will spill over if the number of DataArrays to process does not divide evenly by the number of threads. // Now update the feature Ids auto featureIDataArray = m_DataStructure.getSharedDataAs(m_InputValues->FeatureIdsArrayPath); taskRunner.setParallelizationEnabled(false); // Do this to make the next call synchronous - taskRunner.execute(ErodeDilateBadDataTransferDataImpl(this, totalPoints, m_InputValues->Operation, featureIds, neighbors, featureIDataArray, messageHelper)); + taskRunner.execute(ErodeDilateBadDataTransferDataImpl(this, totalPoints, m_InputValues->Operation, featureIds, neighbors, featureIDataArray)); } return {}; diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ErodeDilateBadData.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ErodeDilateBadData.hpp index 5eb5a734b4..3567816c7a 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ErodeDilateBadData.hpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ErodeDilateBadData.hpp @@ -5,8 +5,11 @@ #include "simplnx/DataStructure/DataPath.hpp" #include "simplnx/DataStructure/DataStructure.hpp" #include "simplnx/Filter/IFilter.hpp" +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" + #include "simplnx/Parameters/ChoicesParameter.hpp" #include "simplnx/Parameters/MultiArraySelectionParameter.hpp" +#include namespace nx::core { @@ -51,11 +54,19 @@ class SIMPLNXCORE_EXPORT ErodeDilateBadData const std::atomic_bool& getCancel(); + /** + * @brief Thread-safe progress update. Safe to call from the parallel per-array workers. + * @param message Fully rendered progress text for one array + */ + void sendThreadSafeProgressMessage(const std::string& message); + private: DataStructure& m_DataStructure; const ErodeDilateBadDataInputValues* m_InputValues = nullptr; const std::atomic_bool& m_ShouldCancel; const IFilter::MessageHandler& m_MessageHandler; + mutable std::mutex m_ProgressMessage_Mutex; + ThrottledMessageHandler m_Throttle; }; } // namespace nx::core diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ErodeDilateMask.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ErodeDilateMask.cpp index cbe1424a67..3a1f55a7ed 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ErodeDilateMask.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ErodeDilateMask.cpp @@ -49,7 +49,7 @@ Result<> ErodeDilateMask::operator()() for(int32_t iteration = 0; iteration < m_InputValues->NumIterations; iteration++) { - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Iteration {}", iteration)); + m_MessageHandler.sendInfoMessage(fmt::format("Iteration {}", iteration)); for(size_t j = 0; j < totalPoints; j++) { diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ExtractComponentAsArray.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ExtractComponentAsArray.cpp index 977acdafa3..3a962fd1b2 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ExtractComponentAsArray.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ExtractComponentAsArray.cpp @@ -91,7 +91,7 @@ Result<> ExtractComponentAsArray::operator()() const auto compToRemoveNum = static_cast(abs(m_InputValues->CompNumber)); // this will be the original array if components are not being removed, else it is resized array auto* baseArrayPtr = m_DataStructure.getDataAs(m_InputValues->BaseArrayPath); - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Extracting Component")); + m_MessageHandler.sendInfoMessage(fmt::format("Extracting Component")); if((!removeComponentsFromArrayBool) && moveComponentsToNewArrayBool) { @@ -103,12 +103,12 @@ Result<> ExtractComponentAsArray::operator()() if(moveComponentsToNewArrayBool) { - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Moving Component")); + m_MessageHandler.sendInfoMessage(fmt::format("Moving Component")); auto* extractedCompArrayPtr = m_DataStructure.getDataAs(m_InputValues->NewArrayPath); ExecuteDataFunction(ExtractComponentsFunctor{}, tempArrayPtr->getDataType(), tempArrayPtr, extractedCompArrayPtr, compToRemoveNum); } - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Removing Original Component")); + m_MessageHandler.sendInfoMessage(fmt::format("Removing Original Component")); // remove by default, because the only case where they weren't removed was covered at start ExecuteDataFunction(RemoveComponentsFunctor{}, tempArrayPtr->getDataType(), tempArrayPtr, baseArrayPtr, compToRemoveNum); diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ExtractFeatureBoundaries2D.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ExtractFeatureBoundaries2D.cpp index 40b1456a02..72d7302188 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ExtractFeatureBoundaries2D.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ExtractFeatureBoundaries2D.cpp @@ -504,7 +504,7 @@ Result<> ExtractFeatureBoundaries2D::operator()() // Get the data type to dispatch to the correct template instantiation const DataType dataType = featureIdsArray.getDataType(); - m_MessageHandler(IFilter::Message::Type::Info, "Extracting feature boundaries..."); + m_MessageHandler.sendInfoMessage("Extracting feature boundaries..."); // Normally FeatureIds are int32 values, but this filter allows the use of any integer types. // Due to this, we need to use the `ExecuteDataFunction` design. diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ExtractVertexGeometry.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ExtractVertexGeometry.cpp index 080acc9091..e13e2d93a1 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ExtractVertexGeometry.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ExtractVertexGeometry.cpp @@ -96,7 +96,7 @@ Result<> ExtractVertexGeometry::operator()() // Copy the mask array into a std::vector. This is just easier in // case the mask array is indeed one of the copied or moved arrays. - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Preparing arrays for extraction...")); + m_MessageHandler.sendInfoMessage(fmt::format("Preparing arrays for extraction...")); std::vector maskedPoints; if(m_InputValues->UseMask) @@ -127,7 +127,7 @@ Result<> ExtractVertexGeometry::operator()() // Use the APIs from the IGeometryGrid to get the XYZ coord for the center // of each cell and then set that into the new VertexGeometry - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Generating vertex geometry")); + m_MessageHandler.sendInfoMessage(fmt::format("Generating vertex geometry")); IGeometry::SharedVertexList& vertices = vertexGeometry.getVerticesRef(); auto& verticesDataStore = vertices.getDataStoreRef(); @@ -150,7 +150,7 @@ Result<> ExtractVertexGeometry::operator()() } } - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Copying cell data to vertex geometry")); + m_MessageHandler.sendInfoMessage(fmt::format("Copying cell data to vertex geometry")); // Since we made copies of the DataArrays, we can safely resize the entire Attribute Matrix, // which will resize all the contained DataArrays diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/FeatureFaceCurvature.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/FeatureFaceCurvature.cpp index 2d4b7cd3cc..011d07dfbb 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/FeatureFaceCurvature.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/FeatureFaceCurvature.cpp @@ -4,8 +4,8 @@ #include "SimplnxCore/Filters/Algorithms/FindNRingNeighbors.hpp" #include "simplnx/DataStructure/DataArray.hpp" -#include "simplnx/Utilities/MessageHelper.hpp" #include "simplnx/Utilities/ParallelTaskAlgorithm.hpp" +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" #include @@ -20,6 +20,7 @@ FeatureFaceCurvature::FeatureFaceCurvature(DataStructure& dataStructure, const I , m_InputValues(inputValues) , m_ShouldCancel(shouldCancel) , m_MessageHandler(mesgHandler) +, m_Throttle(mesgHandler) { } @@ -32,6 +33,13 @@ const std::atomic_bool& FeatureFaceCurvature::getCancel() return m_ShouldCancel; } +// ----------------------------------------------------------------------------- +void FeatureFaceCurvature::sendThreadSafeProgressMessage(usize counter) +{ + std::lock_guard guard(m_ProgressMessage_Mutex); + m_Throttle.incrementCount(counter); +} + // ----------------------------------------------------------------------------- Result<> FeatureFaceCurvature::operator()() { @@ -90,8 +98,6 @@ Result<> FeatureFaceCurvature::operator()() sharedFeatureFaces[surfaceMeshFeatureFaceIds[t]].push_back(t); } - MessageHelper messageHelper(m_MessageHandler); - /********************************* * We are going to specifically invoke TBB directly instead of using ParallelTaskAlgorithm since we can just queue up all * the tasks while the first tasks start up. TBB will then grab a new task from it's own queue to work on it up to the @@ -101,11 +107,10 @@ Result<> FeatureFaceCurvature::operator()() */ #ifdef SIMPLNX_ENABLE_MULTICORE std::shared_ptr g(new tbb::task_group); - messageHelper.sendMessage(fmt::format("Adding {} Feature Faces to the work queue....", maxFaceId)); + m_MessageHandler.sendInfoMessage(fmt::format("Adding {} Feature Faces to the work queue....", maxFaceId)); #endif - ProgressMessageHelper progressMessageHelper = messageHelper.createProgressMessageHelper(); - progressMessageHelper.setMaxProgresss(sharedFeatureFaces.size()); + m_Throttle.reset(sharedFeatureFaces.size(), "Calculating Curvatures"); for(auto& sharedFeatureFace : sharedFeatureFaces) { @@ -114,14 +119,14 @@ Result<> FeatureFaceCurvature::operator()() CalculateTriangleGroupCurvatures func(this, m_InputValues->NRingCount, triangleIds, m_InputValues->useNormalsForCurveFitting, surfaceMeshPrincipalCurvature1sArrayPtr, surfaceMeshPrincipalCurvature2sArrayPtr, surfaceMeshPrincipalDirection1sArrayPtr, surfaceMeshPrincipalDirection2sArrayPtr, surfaceMeshGaussianCurvaturesArrayPtr, surfaceMeshMeanCurvaturesArrayPtr, surfaceMeshWeingartenMatrixArrayPtr, triangleGeomPtr, surfaceMeshFaceLabelsArrayPtr, - surfaceMeshFaceNormalsArrayPtr, surfaceMeshTriangleCentroidsArrayPtr, m_MessageHandler, m_ShouldCancel, progressMessageHelper); + surfaceMeshFaceNormalsArrayPtr, surfaceMeshTriangleCentroidsArrayPtr, m_MessageHandler, m_ShouldCancel); #ifdef SIMPLNX_ENABLE_MULTICORE { g->run(func); } #else - messageHelper.sendMessage(fmt::format("Working on Face Id {}/{}", std::to_string((sharedFeatureFace).first), std::to_string(maxFaceId))); + m_MessageHandler.sendInfoMessage(fmt::format("Working on Face Id {}/{}", std::to_string((sharedFeatureFace).first), std::to_string(maxFaceId))); { func(); } @@ -129,7 +134,7 @@ Result<> FeatureFaceCurvature::operator()() } #ifdef SIMPLNX_ENABLE_MULTICORE - messageHelper.sendMessage("Waiting on computations to complete."); + m_MessageHandler.sendInfoMessage("Waiting on computations to complete."); g->wait(); // Wait for all the threads to complete before moving on. #endif diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/FeatureFaceCurvature.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/FeatureFaceCurvature.hpp index 13259a1213..c2a6302232 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/FeatureFaceCurvature.hpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/FeatureFaceCurvature.hpp @@ -6,6 +6,9 @@ #include "simplnx/DataStructure/DataPath.hpp" #include "simplnx/DataStructure/DataStructure.hpp" #include "simplnx/Filter/IFilter.hpp" +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" + +#include namespace nx::core { @@ -49,11 +52,19 @@ class SIMPLNXCORE_EXPORT FeatureFaceCurvature const std::atomic_bool& getCancel(); + /** + * @brief Thread-safe progress update. Safe to call from the per-feature-face workers. + * @param counter Items completed since the previous call + */ + void sendThreadSafeProgressMessage(usize counter); + private: DataStructure& m_DataStructure; const FeatureFaceCurvatureInputValues* m_InputValues = nullptr; const std::atomic_bool& m_ShouldCancel; const IFilter::MessageHandler& m_MessageHandler; + mutable std::mutex m_ProgressMessage_Mutex; + ThrottledMessageHandler m_Throttle; }; } // namespace nx::core diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/FillBadData.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/FillBadData.cpp index 873d670399..f5e3265251 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/FillBadData.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/FillBadData.cpp @@ -4,8 +4,8 @@ #include "simplnx/DataStructure/Geometry/ImageGeom.hpp" #include "simplnx/Utilities/DataGroupUtilities.hpp" #include "simplnx/Utilities/FilterUtilities.hpp" -#include "simplnx/Utilities/MessageHelper.hpp" #include "simplnx/Utilities/NeighborUtilities.hpp" +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" #include #include @@ -571,9 +571,7 @@ void FillBadData::phaseFourIterativeFill(Int32AbstractDataStore& featureIdsStore voxelArrayNames = allChildArrays.value(); } - // Create a message helper for throttled progress updates (1 update per second) - MessageHelper messageHelper(m_MessageHandler, std::chrono::milliseconds(1000)); - auto throttledMessenger = messageHelper.createThrottledMessenger(std::chrono::milliseconds(1000)); + ThrottledMessageHandler throttle(m_MessageHandler, std::chrono::milliseconds(1000)); usize count = 1; // Number of voxels with -1 value that remain usize iteration = 0; // Current iteration number @@ -667,8 +665,7 @@ void FillBadData::phaseFourIterativeFill(Int32AbstractDataStore& featureIdsStore // progress, so stop instead of looping forever. Remaining -1 voxels are left unchanged. if(count != 0 && !madeAssignment) { - m_MessageHandler( - {IFilter::Message::Type::Warning, fmt::format(" {} bad-data voxel(s) could not be filled: they have no adjacent good-data neighbor. Stopping after {} iteration(s).", count, iteration)}); + m_MessageHandler.sendWarningMessage(fmt::format(" {} bad-data voxel(s) could not be filled: they have no adjacent good-data neighbor. Stopping after {} iteration(s).", count, iteration)); break; } @@ -692,11 +689,11 @@ void FillBadData::phaseFourIterativeFill(Int32AbstractDataStore& featureIdsStore FillBadDataUpdateTuples(featureIdsStore, featureIdsStore, neighbors); // Send throttled progress update (max 1 per second) - throttledMessenger.sendThrottledMessage([iteration, count]() { return fmt::format(" Iteration {}: {} voxels remaining to fill", iteration, count); }); + throttle.queueMessage(" Iteration {}: {} voxels remaining to fill", iteration, count); } // Send final completion summary - m_MessageHandler({IFilter::Message::Type::Info, fmt::format(" Completed in {} iteration{}", iteration, iteration == 1 ? "" : "s")}); + m_MessageHandler.sendInfoMessage(fmt::format(" Completed in {} iteration{}", iteration, iteration == 1 ? "" : "s")); } // ============================================================================= @@ -764,19 +761,19 @@ Result<> FillBadData::operator()() const std::unordered_set smallRegions; // Set of small region roots (unused currently) // Phase 1: Chunk-Sequential Connected Component Labeling - m_MessageHandler({IFilter::Message::Type::Info, "Phase 1/4: Labeling connected components..."}); + m_MessageHandler.sendInfoMessage("Phase 1/4: Labeling connected components..."); phaseOneCCL(featureIdsStore, unionFind, provisionalLabels, dims); // Phase 2: Global Resolution of equivalences - m_MessageHandler({IFilter::Message::Type::Info, "Phase 2/4: Resolving region equivalences..."}); + m_MessageHandler.sendInfoMessage("Phase 2/4: Resolving region equivalences..."); phaseTwoGlobalResolution(unionFind, smallRegions); // Phase 3: Relabeling based on region size classification - m_MessageHandler({IFilter::Message::Type::Info, "Phase 3/4: Classifying region sizes..."}); + m_MessageHandler.sendInfoMessage("Phase 3/4: Classifying region sizes..."); phaseThreeRelabeling(featureIdsStore, cellPhasesPtr, provisionalLabels, smallRegions, unionFind, maxPhase); // Phase 4: Iterative morphological fill - m_MessageHandler({IFilter::Message::Type::Info, "Phase 4/4: Filling small defects..."}); + m_MessageHandler.sendInfoMessage("Phase 4/4: Filling small defects..."); phaseFourIterativeFill(featureIdsStore, dims, numFeatures); return {}; diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/HierarchicalSmooth.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/HierarchicalSmooth.cpp index 3dc6fb000c..403b3d97fc 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/HierarchicalSmooth.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/HierarchicalSmooth.cpp @@ -6,7 +6,7 @@ #include "simplnx/DataStructure/DataArray.hpp" #include "simplnx/DataStructure/Geometry/TriangleGeom.hpp" -#include "simplnx/Utilities/MessageHelper.hpp" +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" #include #include @@ -667,8 +667,7 @@ Result<> runHierarchicalSmooth(VolumeSolverData& vs, const std::atomic_bool& sho int boundaryCount = 1; int totalBoundaries = static_cast(vs.boundaryDict.size()); - MessageHelper messageHelper(messageHandler, std::chrono::milliseconds(1000)); - auto throttledMessenger = messageHelper.createThrottledMessenger(std::chrono::milliseconds(1000)); + ThrottledMessageHandler throttledMessenger(messageHandler, std::chrono::milliseconds(1000)); for(auto it = vs.boundaryDict.begin(); it != vs.boundaryDict.end(); ++it) { @@ -677,7 +676,7 @@ Result<> runHierarchicalSmooth(VolumeSolverData& vs, const std::atomic_bool& sho return {}; } - throttledMessenger.sendThrottledMessage([boundaryCount, totalBoundaries]() { return fmt::format("Processing boundary {} of {}", boundaryCount, totalBoundaries); }); + throttledMessenger.updateCount("Processing boundary", boundaryCount, totalBoundaries); trimesh triSub = sliceMesh(vs, it->second); Triangulation tri(triSub); @@ -784,11 +783,11 @@ Result<> runHierarchicalSmooth(VolumeSolverData& vs, const std::atomic_bool& sho if(rejectedCount > 0) { - messageHandler(IFilter::Message::Type::Warning, fmt::format("{} of {} nodes not smoothed due to excessive displacement", rejectedCount, vs.nodeSmooth.cols())); + messageHandler.sendWarningMessage(fmt::format("{} of {} nodes not smoothed due to excessive displacement", rejectedCount, vs.nodeSmooth.cols())); } else { - messageHandler(IFilter::Message::Type::Info, "All nodes smoothed successfully."); + messageHandler.sendInfoMessage("All nodes smoothed successfully."); } return {}; @@ -828,7 +827,7 @@ Result<> HierarchicalSmooth::operator()() IGeometry::MeshIndexType numVertices = triangleGeom.getNumberOfVertices(); IGeometry::MeshIndexType numFaces = triangleGeom.getNumberOfFaces(); - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Hierarchical Smooth: {} vertices, {} faces", numVertices, numFaces)); + m_MessageHandler.sendInfoMessage(fmt::format("Hierarchical Smooth: {} vertices, {} faces", numVertices, numFaces)); // Convert simplnx data to Eigen types // meshnode is 3 x N (each column is a vertex) @@ -875,7 +874,7 @@ Result<> HierarchicalSmooth::operator()() } // Write smoothed vertices back to the triangle geometry - m_MessageHandler(IFilter::Message::Type::Info, "Writing smoothed vertices back to geometry..."); + m_MessageHandler.sendInfoMessage("Writing smoothed vertices back to geometry..."); for(IGeometry::MeshIndexType i = 0; i < numVertices; i++) { verticesRef[3 * i + 0] = static_cast(vs.nodeSmooth(0, i)); diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/IdentifySample.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/IdentifySample.cpp index 0d8f20c01d..a1a76a7601 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/IdentifySample.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/IdentifySample.cpp @@ -3,8 +3,8 @@ #include "simplnx/DataStructure/DataArray.hpp" #include "simplnx/DataStructure/Geometry/ImageGeom.hpp" #include "simplnx/Utilities/FilterUtilities.hpp" -#include "simplnx/Utilities/MessageHelper.hpp" #include "simplnx/Utilities/NeighborUtilities.hpp" +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" using namespace nx::core; @@ -18,8 +18,7 @@ struct IdentifySampleFunctor { constexpr FaceNeighborType k_NeighborCount = VoxelNeighbors::k_FaceNeighborCount; - MessageHelper messageHelper(messageHandler); - ThrottledMessenger throttledMessenger = messageHelper.createThrottledMessenger(); + ThrottledMessageHandler throttledMessenger(messageHandler); ShapeType cDims = {1}; auto& goodVoxels = goodVoxelsPtr->template getIDataStoreRefAs>(); @@ -51,7 +50,7 @@ struct IdentifySampleFunctor for(int64 yLoopIdx = 0; yLoopIdx < dims[1]; yLoopIdx++) { const int64 yStride = dims[0] * yLoopIdx; - throttledMessenger.sendThrottledMessage([&] { return fmt::format("Identifying potential samples || {:.2f}% Complete", CalculatePercentComplete(zStride + yStride, totalPoints)); }); + throttledMessenger.updatePercent("Identifying potential samples", zStride + yStride, totalPoints); if(shouldCancel) { return; @@ -115,14 +114,14 @@ struct IdentifySampleFunctor // This is done by flipping all 'bad' voxel features that do not touch the outside of the sample (i.e. they are fully contained inside the 'sample'). if(fillHoles) { - messageHelper.sendMessage("Filling holes in sample..."); + messageHandler.sendInfoMessage("Filling holes in sample..."); for(int64 zLoopIdx = 0; zLoopIdx < dims[2]; zLoopIdx++) { const int64 zStride = dims[0] * dims[1] * zLoopIdx; for(int64 yLoopIdx = 0; yLoopIdx < dims[1]; yLoopIdx++) { const int64 yStride = dims[0] * yLoopIdx; - throttledMessenger.sendThrottledMessage([&] { return fmt::format("Identifying potential samples || {:.2f}% Complete", CalculatePercentComplete(zStride + yStride, totalPoints)); }); + throttledMessenger.updatePercent("Identifying potential samples", zStride + yStride, totalPoints); if(shouldCancel) { return; @@ -240,7 +239,7 @@ struct IdentifySampleSliceBySliceFunctor { return; } - messageHandler(IFilter::Message::Type::Info, fmt::format("Slice {}", fixedIdx)); + messageHandler.sendInfoMessage(fmt::format("Slice {}", fixedIdx)); std::vector checked(planeDim1 * planeDim2, false); std::vector sample(planeDim1 * planeDim2, false); diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/InterpolatePointCloudToRegularGrid.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/InterpolatePointCloudToRegularGrid.cpp index 4a1cca676c..74d921616c 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/InterpolatePointCloudToRegularGrid.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/InterpolatePointCloudToRegularGrid.cpp @@ -297,13 +297,13 @@ Result<> InterpolatePointCloudToRegularGrid::operator()() if(i > prog) { const auto progressInt = static_cast((static_cast(i) / static_cast(numVerts)) * 100.0); - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Interpolating Point Cloud || {}% Completed", progressInt)); + m_MessageHandler.sendInfoMessage(fmt::format("Interpolating Point Cloud || {}% Completed", progressInt)); prog += progIncrement; } } // Finalization pass - write outputs - m_MessageHandler(IFilter::Message::Type::Info, "Writing interpolated results..."); + m_MessageHandler.sendInfoMessage("Writing interpolated results..."); for(usize a = 0; a < numInterpArrays; a++) { diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/IterativeClosestPoint.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/IterativeClosestPoint.cpp index f51aed5130..38f2444865 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/IterativeClosestPoint.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/IterativeClosestPoint.cpp @@ -71,7 +71,7 @@ IterativeClosestPoint::~IterativeClosestPoint() noexcept = default; // ----------------------------------------------------------------------------- void IterativeClosestPoint::updateProgress(const std::string& message) { - m_MessageHandler(IFilter::Message::Type::Info, message); + m_MessageHandler.sendInfoMessage(message); } // ----------------------------------------------------------------------------- @@ -126,7 +126,7 @@ Result<> IterativeClosestPoint::operator()() using Adaptor = VertexGeomAdaptor; const Adaptor adaptor(targetVertexGeom); - m_MessageHandler("Building kd-tree index..."); + m_MessageHandler.sendInfoMessage("Building kd-tree index..."); using KDtree = nanoflann::KDTreeSingleIndexAdaptor, Adaptor, 3>; KDtree index(3, adaptor, nanoflann::KDTreeSingleIndexAdaptorParams(30)); @@ -177,7 +177,7 @@ Result<> IterativeClosestPoint::operator()() auto now = std::chrono::steady_clock::now(); if(std::chrono::duration_cast(now - start).count() > 1000) { - m_MessageHandler(fmt::format("Performing Registration Iterations || {}% Completed", static_cast((static_cast(i) / m_InputValues->NumIterations) * 100.0f))); + m_MessageHandler.sendInfoMessage(fmt::format("Performing Registration Iterations || {}% Completed", static_cast((static_cast(i) / m_InputValues->NumIterations) * 100.0f))); start = now; } } diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/LaplacianSmoothing.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/LaplacianSmoothing.cpp index 7e53742e38..26e12d44ea 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/LaplacianSmoothing.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/LaplacianSmoothing.cpp @@ -78,7 +78,7 @@ Result<> LaplacianSmoothing::edgeBasedSmoothing() { return {}; } - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Iteration {} of {}", q, m_InputValues->pIterationSteps)); + m_MessageHandler.sendInfoMessage(fmt::format("Iteration {} of {}", q, m_InputValues->pIterationSteps)); // Compute the Deltas for each point for(IGeometry::MeshIndexType i = 0; i < numEdges; i++) { @@ -124,7 +124,7 @@ Result<> LaplacianSmoothing::edgeBasedSmoothing() { return {}; } - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Iteration {} of {}", q, m_InputValues->pIterationSteps)); + m_MessageHandler.sendInfoMessage(fmt::format("Iteration {} of {}", q, m_InputValues->pIterationSteps)); // Compute the Delta's for(IGeometry::MeshIndexType i = 0; i < numEdges; i++) { diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/M3CSurfaceMeshing.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/M3CSurfaceMeshing.cpp index 226dee276c..c8ce1cad81 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/M3CSurfaceMeshing.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/M3CSurfaceMeshing.cpp @@ -2565,7 +2565,7 @@ Result<> finalizeMesh(DataStructure& dataStructure, const M3CSurfaceMeshingInput } // --- Stage 5: compact nodes and write the output TriangleGeom -------------- - messageHandler("Writing surface mesh..."); + messageHandler.sendInfoMessage("Writing surface mesh..."); // Node-id compaction without a dense 7*numSites candidate->id map. A candidate's compacted id is simply // the number of real nodes (nodeType > 0) that precede it; we answer that from a coarse per-block // prefix over nodeType plus a small in-block scan (saves ~3.8 GB at 512^3 vs a uint32 map). This is @@ -2656,7 +2656,7 @@ Result<> finalizeMesh(DataStructure& dataStructure, const M3CSurfaceMeshingInput // exterior side (FaceLabel == -1) is skipped by the transfer functions. if(!inputValues->SelectedCellDataArrayPaths.empty() || !inputValues->SelectedFeatureDataArrayPaths.empty()) { - messageHandler("Transferring attribute arrays to the mesh faces..."); + messageHandler.sendInfoMessage("Transferring attribute arrays to the mesh faces..."); std::vector> transfers; for(usize i = 0; i < inputValues->SelectedCellDataArrayPaths.size(); i++) { @@ -2699,13 +2699,13 @@ Result<> finalizeMesh(DataStructure& dataStructure, const M3CSurfaceMeshingInput // so this pass (using triangle connectivity) makes the winding consistent with the FaceLabels. if(inputValues->RepairTriangleWinding) { - messageHandler("Generating connectivity and triangle neighbors..."); + messageHandler.sendInfoMessage("Generating connectivity and triangle neighbors..."); triangleGeom.findElementNeighbors(true); const auto optionalId = triangleGeom.getElementNeighborsId(); if(optionalId.has_value()) { const auto& connectivity = dataStructure.getDataRefAs(optionalId.value()); - messageHandler("Repairing windings..."); + messageHandler.sendInfoMessage("Repairing windings..."); Result<> windingResult = MeshingUtilities::RepairTriangleWinding(triangleGeom.getFaces()->getDataStoreRef(), connectivity, dataStructure.getDataAs(inputValues->FaceLabelsDataPath)->getDataStoreRef(), shouldCancel, messageHandler); dataStructure.removeData(triangleGeom.getElementContainingVertId().value()); @@ -2787,7 +2787,7 @@ Result<> M3CSurfaceMeshing::runEntireVolume() // Read FeatureIds directly from its DataStore (no full-array copy) into the padded working grid. // The FeatureId==0 renumbering happens on the working grid, never on the input array. - m_MessageHandler("Initializing working grid and ghost layer..."); + m_MessageHandler.sendInfoMessage("Initializing working grid and ghost layer..."); std::vector point(totalPoints + 1, 0); const int maxGrainId = initialize_micro(k_AddSurfaceLayer, dims, fileDim, featureIdsStore, point.data()); @@ -2797,7 +2797,7 @@ Result<> M3CSurfaceMeshing::runEntireVolume() const NeighborAccessor neighbors{numSites, numSitesPerPlane, static_cast(fileDim[0])}; // 3 marching squares (top/back/left) per site; node types (7 candidate nodes/site) start Unused (0). - m_MessageHandler("Initializing candidate nodes and squares..."); + m_MessageHandler.sendInfoMessage("Initializing candidate nodes and squares..."); std::vector squares(static_cast(3) * numSites); std::vector nodeType(static_cast(7) * numSites, 0); initialize_squares(squares.data(), numSites); @@ -2808,10 +2808,10 @@ Result<> M3CSurfaceMeshing::runEntireVolume() } // --- Stage 2: face edges --------------------------------------------------- - m_MessageHandler("Counting face edges..."); + m_MessageHandler.sendInfoMessage("Counting face edges..."); const int64 nFEdge = get_number_fEdges(squares.data(), point.data(), neighbors, numSites, m_ShouldCancel); - m_MessageHandler("Finding nodes and edges on each square..."); + m_MessageHandler.sendInfoMessage("Finding nodes and edges on each square..."); std::vector fedges(static_cast(nFEdge < 0 ? 0 : nFEdge)); get_nodes_fEdges(squares.data(), point.data(), neighbors, nodeType.data(), fedges.data(), numSites, numSitesPerPlane, static_cast(fileDim[0]), m_ShouldCancel); @@ -2821,10 +2821,10 @@ Result<> M3CSurfaceMeshing::runEntireVolume() } // --- Stage 3: triangles ---------------------------------------------------- - m_MessageHandler("Counting triangles..."); + m_MessageHandler.sendInfoMessage("Counting triangles..."); const int64 nTriangle = get_number_triangles(point.data(), squares.data(), neighbors, nodeType.data(), fedges.data(), numSites, numSitesPerPlane, static_cast(fileDim[0]), m_ShouldCancel); - m_MessageHandler("Generating triangles..."); + m_MessageHandler.sendInfoMessage("Generating triangles..."); std::vector triangles(static_cast(nTriangle < 0 ? 0 : nTriangle)); std::vector mCubeID(static_cast(nTriangle < 0 ? 0 : nTriangle), 0); get_triangles(siteCoords, triangles.data(), mCubeID.data(), squares.data(), nodeCoords, fedges.data(), numSites, numSitesPerPlane, static_cast(fileDim[0]), m_ShouldCancel); @@ -2862,7 +2862,7 @@ Result<> M3CSurfaceMeshing::runWindowed(bool parallel) const SiteId numSitesPerPlane = static_cast(fileDim[0] * fileDim[1]); const int xDim = static_cast(fileDim[0]); - m_MessageHandler("Initializing working grid and ghost layer..."); + m_MessageHandler.sendInfoMessage("Initializing working grid and ghost layer..."); std::vector point(totalPoints + 1, 0); const int maxGrainId = initialize_micro(k_AddSurfaceLayer, dims, fileDim, featureIdsStore, point.data()); @@ -3052,7 +3052,7 @@ Result<> M3CSurfaceMeshing::runWindowed(bool parallel) const int64 sliceIdx = winBaseSite / numSitesPerPlane; if(sliceIdx % progressStep == 0) { - m_MessageHandler(fmt::format("Sweeping z-slices ({}): slice {} / {}", generate ? "pass 2, generating triangles" : "pass 1, counting", sliceIdx, totalSlices)); + m_MessageHandler.sendInfoMessage(fmt::format("Sweeping z-slices ({}): slice {} / {}", generate ? "pass 2, generating triangles" : "pass 1, counting", sliceIdx, totalSlices)); } const SiteId newLoSquare = 3 * (winBaseSite + numSitesPerPlane - 1); const SiteId newHiSquare = std::min(3 * (winBaseSite + 2 * numSitesPerPlane - 1), 3 * numSites); @@ -3194,7 +3194,7 @@ Result<> M3CSurfaceMeshing::runWindowed(bool parallel) if(!parallel) { - m_MessageHandler("Sweeping z-slices (pass 1: face edges + triangle count)..."); + m_MessageHandler.sendInfoMessage("Sweeping z-slices (pass 1: face edges + triangle count)..."); sweep(true, false); if(m_ShouldCancel) { @@ -3203,7 +3203,7 @@ Result<> M3CSurfaceMeshing::runWindowed(bool parallel) triangles.resize(static_cast(nTriangle < 0 ? 0 : nTriangle)); mCubeID.resize(static_cast(nTriangle < 0 ? 0 : nTriangle), 0); - m_MessageHandler("Sweeping z-slices (pass 2: generating triangles)..."); + m_MessageHandler.sendInfoMessage("Sweeping z-slices (pass 2: generating triangles)..."); sweep(false, true); } else @@ -3360,7 +3360,7 @@ Result<> M3CSurfaceMeshing::runWindowed(bool parallel) std::vector triOffset(numCubes + 1, 0); // 1-based per-cube; pass 1 fills counts, then prefix -> offsets - m_MessageHandler("Sweeping z-slices (parallel pass 1: counting)..."); + m_MessageHandler.sendInfoMessage("Sweeping z-slices (parallel pass 1: counting)..."); { winBaseSite = 1; int64 eid = 0; @@ -3394,7 +3394,7 @@ Result<> M3CSurfaceMeshing::runWindowed(bool parallel) triangles.resize(static_cast(total)); mCubeID.resize(static_cast(total), 0); - m_MessageHandler("Sweeping z-slices (parallel pass 2: generating triangles)..."); + m_MessageHandler.sendInfoMessage("Sweeping z-slices (parallel pass 2: generating triangles)..."); { winBaseSite = 1; int64 eid = 0; diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/MapPointCloudToRegularGrid.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/MapPointCloudToRegularGrid.cpp index 7751d0801b..de15cbb021 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/MapPointCloudToRegularGrid.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/MapPointCloudToRegularGrid.cpp @@ -83,7 +83,7 @@ Result<> ProcessVertices(const IFilter::MessageHandler& messageHandler, const Ve auto now = std::chrono::steady_clock::now(); if(std::chrono::duration_cast(now - start).count() > 1000) { - messageHandler(fmt::format("Computing Point Cloud Voxel Indices || {}% Completed", static_cast((static_cast(i) / numVerts) * 100.0f))); + messageHandler.sendInfoMessage(fmt::format("Computing Point Cloud Voxel Indices || {}% Completed", static_cast((static_cast(i) / numVerts) * 100.0f))); start = now; } } @@ -113,12 +113,6 @@ MapPointCloudToRegularGrid::MapPointCloudToRegularGrid(DataStructure& dataStruct // ----------------------------------------------------------------------------- MapPointCloudToRegularGrid::~MapPointCloudToRegularGrid() noexcept = default; -// ----------------------------------------------------------------------------- -void MapPointCloudToRegularGrid::updateProgress(const std::string& message) -{ - m_MessageHandler(IFilter::Message::Type::Info, message); -} - // ----------------------------------------------------------------------------- const std::atomic_bool& MapPointCloudToRegularGrid::getCancel() { diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/MapPointCloudToRegularGrid.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/MapPointCloudToRegularGrid.hpp index 0b1f5db8ee..fbd24dce59 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/MapPointCloudToRegularGrid.hpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/MapPointCloudToRegularGrid.hpp @@ -36,7 +36,6 @@ class SIMPLNXCORE_EXPORT MapPointCloudToRegularGrid MapPointCloudToRegularGrid& operator=(MapPointCloudToRegularGrid&&) noexcept = delete; Result<> operator()(); - void updateProgress(const std::string& message); const std::atomic_bool& getCancel(); private: diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/PadImageGeometry.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/PadImageGeometry.cpp index e6dc4a4b3f..6fb55a06fc 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/PadImageGeometry.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/PadImageGeometry.cpp @@ -152,7 +152,7 @@ Result<> PadImageGeometry::operator()() auto& newDataArray = dynamic_cast(destCellDataAM.at(srcName)); - m_MessageHandler(fmt::format("Padding Volume || Copying Data Array {}", srcName)); + m_MessageHandler.sendInfoMessage(fmt::format("Padding Volume || Copying Data Array {}", srcName)); ExecuteParallelFunction(oldDataArray.getDataType(), taskRunner, oldDataArray, newDataArray, srcImageGeom, m_InputValues, m_ShouldCancel); } taskRunner.wait(); // This will spill over if the number of DataArrays to process does not divide evenly by the number of threads. diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/PointSampleEdgeGeometry.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/PointSampleEdgeGeometry.cpp index af12bc6052..cf68cc0153 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/PointSampleEdgeGeometry.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/PointSampleEdgeGeometry.cpp @@ -162,7 +162,7 @@ Result<> PointSampleEdgeGeometry::operator()() } // --- Step 1: Count how many total sample points to generate --- - m_MessageHandler(IFilter::Message::Type::Info, "Computing total sampling points..."); + m_MessageHandler.sendInfoMessage("Computing total sampling points..."); usize numVertices = 0; auto& edgeVertices = edgeGeom.getVerticesRef(); @@ -182,7 +182,7 @@ Result<> PointSampleEdgeGeometry::operator()() numVertices += result.value(); } - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Total sampling points: {}", numVertices)); + m_MessageHandler.sendInfoMessage(fmt::format("Total sampling points: {}", numVertices)); // Resize the vertex geometry and the vertex attribute matrix auto& vertices = vertexGeom.getVerticesRef(); @@ -191,7 +191,7 @@ Result<> PointSampleEdgeGeometry::operator()() vertexAttrMatrix.resizeTuples({numVertices}); // --- Step 2: Generate and write each sampled point --- - m_MessageHandler(IFilter::Message::Type::Info, "Generating sampled points along edge geometry..."); + m_MessageHandler.sendInfoMessage("Generating sampled points along edge geometry..."); int64 vertCount = 0; for(int64 i = 0; i < numEdges; i++) { @@ -204,7 +204,7 @@ Result<> PointSampleEdgeGeometry::operator()() usize maxEdgeId = *std::max_element(vertexEdgeIdsDataStore.begin(), vertexEdgeIdsDataStore.end()); - m_MessageHandler(IFilter::Message::Type::Info, "Copying Edge Data to Vertex Geometry..."); + m_MessageHandler.sendInfoMessage("Copying Edge Data to Vertex Geometry..."); const DataPath vertexAttrMatPath = vertexGeom.getVertexAttributeMatrixDataPath(); for(const auto& selectedArrayPath : m_InputValues->pSelectedDataArrayPaths) { @@ -218,7 +218,7 @@ Result<> PointSampleEdgeGeometry::operator()() vertexEdgeIdsDataPath.toString(), maxEdgeId, selectedEdgeArray->getNumberOfTuples(), selectedArrayPath.toString())); } - m_MessageHandler(IFilter::ProgressMessage{IFilter::ProgressMessage::Type::Info, fmt::format("Copying data into vertex array '{}'...", createdArrayPath.toString())}); + m_MessageHandler.sendInfoMessage(fmt::format("Copying data into vertex array '{}'...", createdArrayPath.toString())); ParallelDataAlgorithm dataAlg; dataAlg.setRange(0, createdVertexArray->getNumberOfTuples()); ExecuteParallelFunction<::CopyEdgeDataToVertexData>(selectedEdgeArray->getDataType(), dataAlg, selectedEdgeArray, createdVertexArray, vertexEdgeIdsDataStore, m_ShouldCancel); diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/PointSampleTriangleGeometry.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/PointSampleTriangleGeometry.cpp index df402efe5b..3074e86922 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/PointSampleTriangleGeometry.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/PointSampleTriangleGeometry.cpp @@ -165,7 +165,7 @@ Result<> PointSampleTriangleGeometry::operator()() { progressInt = static_cast((static_cast(counter) / static_cast(m_Inputs->pNumberOfSamples)) * 100.0f); std::string ss = fmt::format("Sampling Triangles || {}% Completed", progressInt); - m_MessageHandler(IFilter::Message::Type::Info, ss); + m_MessageHandler.sendInfoMessage(ss); prog = prog + progIncrement; } counter++; diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/QuickSurfaceMesh.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/QuickSurfaceMesh.cpp index e0016d3d54..4082eef5e7 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/QuickSurfaceMesh.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/QuickSurfaceMesh.cpp @@ -388,7 +388,7 @@ Result<> QuickSurfaceMesh::operator()() if(m_InputValues->RepairTriangleWinding) { // Generate Connectivity - m_MessageHandler("Generating Connectivity and Triangle Neighbors..."); + m_MessageHandler.sendInfoMessage("Generating Connectivity and Triangle Neighbors..."); triangleGeom.findElementNeighbors(true); const auto optionalId = triangleGeom.getElementNeighborsId(); if(!optionalId.has_value()) @@ -397,7 +397,7 @@ Result<> QuickSurfaceMesh::operator()() } const auto& connectivity = m_DataStructure.getDataRefAs(optionalId.value()); - m_MessageHandler("Repairing Windings..."); + m_MessageHandler.sendInfoMessage("Repairing Windings..."); windingResult = MeshingUtilities::RepairTriangleWinding(triangleGeom.getFaces()->getDataStoreRef(), connectivity, m_DataStructure.getDataAs(m_InputValues->FaceLabelsDataPath)->getDataStoreRef(), m_ShouldCancel, m_MessageHandler); @@ -482,7 +482,7 @@ Result<> QuickSurfaceMesh::operator()() // ----------------------------------------------------------------------------- void QuickSurfaceMesh::correctProblemVoxels() { - m_MessageHandler(IFilter::Message::Type::Info, "Correcting Problem Voxels"); + m_MessageHandler.sendInfoMessage("Correcting Problem Voxels"); auto* grid = m_DataStructure.getDataAs(m_InputValues->GridGeomDataPath); auto& featureIds = m_DataStructure.getDataAs(m_InputValues->FeatureIdsArrayPath)->getDataStoreRef(); @@ -646,14 +646,14 @@ void QuickSurfaceMesh::correctProblemVoxels() } std::string ss = fmt::format("Correcting Problem Voxels: Iteration - '{}'; Problem Voxels - '{}'", iter, count); - m_MessageHandler(IFilter::Message::Type::Info, ss); + m_MessageHandler.sendInfoMessage(ss); } } // ----------------------------------------------------------------------------- void QuickSurfaceMesh::determineActiveNodes(std::vector& nodeIds, MeshIndexType& nodeCount, MeshIndexType& triangleCount) { - m_MessageHandler(IFilter::Message::Type::Info, "Determining active Nodes"); + m_MessageHandler.sendInfoMessage("Determining active Nodes"); auto* grid = m_DataStructure.getDataAs(m_InputValues->GridGeomDataPath); Int32AbstractDataStore& featureIds = m_DataStructure.getDataAs(m_InputValues->FeatureIdsArrayPath)->getDataStoreRef(); @@ -960,7 +960,7 @@ void QuickSurfaceMesh::createNodesAndTriangles(std::vector& m_Nod { return; } - m_MessageHandler(IFilter::Message::Type::Info, "Creating mesh"); + m_MessageHandler.sendInfoMessage("Creating mesh"); auto& featureIds = m_DataStructure.getDataAs(m_InputValues->FeatureIdsArrayPath)->getDataStoreRef(); @@ -1570,7 +1570,7 @@ void QuickSurfaceMesh::generateTripleLines() * later if needed. * Mike Jackson, JULY 2018 */ - m_MessageHandler(IFilter::Message::Type::Info, "Generating Triple Lines"); + m_MessageHandler.sendInfoMessage("Generating Triple Lines"); Int32AbstractDataStore& featureIds = m_DataStructure.getDataAs(m_InputValues->FeatureIdsArrayPath)->getDataStoreRef(); diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReadBinaryCTNorthstar.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReadBinaryCTNorthstar.cpp index 331757559d..266f48e490 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReadBinaryCTNorthstar.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReadBinaryCTNorthstar.cpp @@ -74,7 +74,7 @@ Result<> ReadBinaryCTFiles(DataStructure& dataStructure, const IFilter::MessageH fileZSlice++; continue; } - messageHandler(fmt::format("Importing Data || Data File: {} || Importing Slice {}", dataFileInput.first.string(), z)); + messageHandler.sendInfoMessage(fmt::format("Importing Data || Data File: {} || Importing Slice {}", dataFileInput.first.string(), z)); for(usize y = 0; y < inputValues->OriginalGeometryDims[1]; y++) { if(inputValues->ImportSubvolume && (y < inputValues->StartVoxelCoord[1] || y > inputValues->EndVoxelCoord[1])) diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReadCSVFile.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReadCSVFile.cpp index e856845619..12790c3175 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReadCSVFile.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReadCSVFile.cpp @@ -12,7 +12,7 @@ void notifyProgress(usize lineNumber, usize numberOfTuples, float32& threshold, if(percentCompleted > threshold) { // Print the status of the import - msgHandler({IFilter::Message::Type::Info, fmt::format("Importing CSV Data || {:.{}f}% Complete", static_cast(percentCompleted), 1)}); + msgHandler.sendInfoMessage(fmt::format("Importing CSV Data || {:.{}f}% Complete", static_cast(percentCompleted), 1)); threshold = threshold + 5.0f; if(threshold < percentCompleted) { diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReadDeformKeyFileV12.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReadDeformKeyFileV12.cpp index 2efdc82501..836cb02df0 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReadDeformKeyFileV12.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReadDeformKeyFileV12.cpp @@ -114,7 +114,7 @@ class IOHandler auto result = readUserDefinedVariables(); if(result.invalid()) { - m_Filter->updateProgress(result.errors().data()->message); + m_Filter->reportWarning(result.errors().data()->message); } continue; } @@ -175,7 +175,7 @@ class IOHandler } // Unrecognized section - m_Filter->updateProgress(fmt::format("Warning at line {}: Unrecognized section", StringUtilities::number(m_LineCount))); + m_Filter->reportWarning(fmt::format("Warning at line {}: Unrecognized section", StringUtilities::number(m_LineCount))); } return {}; @@ -667,7 +667,7 @@ class IOHandler Result<> result = parse_ull(tokens.at(2), numVerts); if(result.invalid()) { - m_Filter->updateProgress(result.errors().data()->message); + m_Filter->reportWarning(result.errors().data()->message); continue; } @@ -694,7 +694,7 @@ class IOHandler Result<> result = parse_ull(tokens.at(2), numCells); if(result.invalid()) { - m_Filter->updateProgress(result.errors().data()->message); + m_Filter->reportWarning(result.errors().data()->message); continue; } @@ -722,7 +722,7 @@ class IOHandler Result<> tupResult = parse_ull(tokens.at(2), tupleCount); if(tupResult.invalid()) { - m_Filter->updateProgress(tupResult.errors().data()->message); + m_Filter->reportWarning(tupResult.errors().data()->message); continue; } @@ -760,13 +760,13 @@ class IOHandler "Unable to read data: {}. Its tuple size ({}) doesn't match the correct number of tuples to be a cell array ({}), and the vertex tuple count has not been read yet. Skipping...", dataArrayName, tupleCount, m_Cache.cellAttrMatTupleCount); } - m_Filter->updateProgress(msg); + m_Filter->reportWarning(msg); } else { std::string msg = fmt::format("Unable to read data: {}. Its tuple size ({}) doesn't match the correct number of tuples to be either a vertex array or cell array. Skipping...", dataArrayName, tupleCount); - m_Filter->updateProgress(msg); + m_Filter->reportWarning(msg); } } if(readInResult.invalid()) @@ -804,10 +804,16 @@ const std::atomic_bool& ReadDeformKeyFileV12::getCancel() return m_ShouldCancel; } +// ----------------------------------------------------------------------------- +void ReadDeformKeyFileV12::reportWarning(const std::string& message) +{ + m_MessageHandler.sendWarningMessage(message); +} + // ----------------------------------------------------------------------------- void ReadDeformKeyFileV12::updateProgress(const std::string& progressMessage) { - m_MessageHandler({IFilter::Message::Type::Info, progressMessage}); + m_MessageHandler.sendInfoMessage(progressMessage); } // ----------------------------------------------------------------------------- diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReadDeformKeyFileV12.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReadDeformKeyFileV12.hpp index 542b62e8b7..09fbe6c5df 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReadDeformKeyFileV12.hpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReadDeformKeyFileV12.hpp @@ -60,6 +60,13 @@ class SIMPLNXCORE_EXPORT ReadDeformKeyFileV12 const std::atomic_bool& getCancel(); void updateProgress(const std::string& progMessage); + + /** + * @brief Reports a warning encountered while parsing. Access shim for the parser helper, which + * cannot reach the message handler directly. + * @param message + */ + void reportWarning(const std::string& message); FileCache& getCache(); private: diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReadImage.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReadImage.cpp index da2b8fdb5d..c2aa7f2c98 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReadImage.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReadImage.cpp @@ -146,7 +146,7 @@ Result<> ReadImage::operator()() { const auto& inputFilePath = m_InputValues.inputFilePath; - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Reading image file: {}", inputFilePath.string())); + m_MessageHandler.sendInfoMessage(fmt::format("Reading image file: {}", inputFilePath.string())); auto imageIOResult = CreateImageIO(inputFilePath); if(imageIOResult.invalid()) @@ -267,7 +267,7 @@ Result<> ReadImage::operator()() if(m_InputValues.changeDataType && srcType != destType) { - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Converting pixel data from {} to {}", DataTypeToString(srcType), DataTypeToString(destType))); + m_MessageHandler.sendInfoMessage(fmt::format("Converting pixel data from {} to {}", DataTypeToString(srcType), DataTypeToString(destType))); auto convResult = ExecuteDataFunction(DispatchConversionFunctor{}, srcType, destType, imageArray, tempBuffer, window); if(convResult.invalid()) { diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReadImageStack.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReadImageStack.cpp index f02129c776..4fa9e1b0fb 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReadImageStack.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReadImageStack.cpp @@ -190,7 +190,7 @@ Result<> ReadImageStackImpl(DataStructure& dataStructure, const ReadImageStackIn for(usize i = startSlice; i <= endSlice; i++) { const std::string& filePath = files[i]; - messageHandler(IFilter::Message::Type::Info, fmt::format("Importing: {}", filePath)); + messageHandler.sendInfoMessage(fmt::format("Importing: {}", filePath)); DataStructure importedDataStructure; { diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReadNIfTIFile.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReadNIfTIFile.cpp index 4f81488f0d..6893e43ae1 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReadNIfTIFile.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReadNIfTIFile.cpp @@ -279,7 +279,7 @@ Result<> StreamCroppedVoxels(gzFile gz, AbstractDataStore& store, const if(destTupleOffset - lastProgressTuples >= k_ProgressTupleStride || destTupleOffset == totalDestTuples) { const auto pct = static_cast((destTupleOffset * 100ULL) / std::max(1, totalDestTuples)); - messageHandler({IFilter::Message::Type::Info, fmt::format("{}% Complete", pct)}); + messageHandler.sendInfoMessage(fmt::format("{}% Complete", pct)); lastProgressTuples = destTupleOffset; } } diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReadStlFile.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReadStlFile.cpp index bab5791a57..e75fab11a3 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReadStlFile.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReadStlFile.cpp @@ -8,8 +8,8 @@ #include "simplnx/DataStructure/Geometry/TriangleGeom.hpp" #include "simplnx/Utilities/DataArrayUtilities.hpp" #include "simplnx/Utilities/GeometryUtilities.hpp" -#include "simplnx/Utilities/MessageHelper.hpp" #include "simplnx/Utilities/StringUtilities.hpp" +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" #include #include @@ -138,14 +138,13 @@ Result<> ReadStlFile::operator()() uint16_t attr = 0; std::vector triangleAttributeBuffer(std::numeric_limits::max()); // Just allocate a buffer of max UINT16 elements - MessageHelper messageHelper(m_MessageHandler); - ThrottledMessenger throttledMessenger = messageHelper.createThrottledMessenger(); + ThrottledMessageHandler throttledMessenger(m_MessageHandler); fpos_t pos; for(int32_t t = 0; t < triCount; ++t) { - throttledMessenger.sendThrottledMessage([&]() { return fmt::format("Reading {:.2f}% Complete", CalculatePercentComplete(t, triCount)); }); + throttledMessenger.updatePercent("Reading", t, triCount); if(m_ShouldCancel) { return {}; diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReadVolumeGraphicsFile.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReadVolumeGraphicsFile.cpp index a74bfa0ec4..6af248f46c 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReadVolumeGraphicsFile.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReadVolumeGraphicsFile.cpp @@ -46,6 +46,6 @@ Result<> ReadVolumeGraphicsFile::operator()() return MakeErrorResult(k_VolBinaryAllocateMismatch, fmt::format("Binary file size ({}) is smaller than the number of allocated bytes ({}).", filesize, allocatedBytes)); } - m_MessageHandler(IFilter::Message::Type::Info, "Reading Data from .vol File....."); + m_MessageHandler.sendInfoMessage("Reading Data from .vol File....."); return ImportFromBinaryFile(m_InputValues->VGDataFile, *densityArray); } diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReadZeissTxmFile.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReadZeissTxmFile.cpp index e64b0b8cb8..87abaf7c85 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReadZeissTxmFile.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReadZeissTxmFile.cpp @@ -181,12 +181,12 @@ Result<> ReadImages(const ReadZeissTxmFileInputValues* inputValues, DataStructur } std::stringstream pathStrm; pathStrm << "/ImageData" << imageGroupIndex << "/Image" << z; - m_MessageHandler({IFilter::Message::Type::Info, fmt::format("Constructing Image Path: {}", pathStrm.str())}); + m_MessageHandler.sendInfoMessage(fmt::format("Constructing Image Path: {}", pathStrm.str())); StreamPtrType stream = std::make_shared(storage.get(), pathStrm.str()); if(!stream->fail()) { - m_MessageHandler({IFilter::Message::Type::Info, fmt::format("Reading Image: {}", pathStrm.str())}); + m_MessageHandler.sendInfoMessage(fmt::format("Reading Image: {}", pathStrm.str())); readBytes = stream->read(reinterpret_cast(buffer.data()), totalBytes); if(readBytes != totalBytes) { diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RegularGridSampleSurfaceMesh.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RegularGridSampleSurfaceMesh.cpp index 9da6018bee..320a2df0b7 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RegularGridSampleSurfaceMesh.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RegularGridSampleSurfaceMesh.cpp @@ -356,7 +356,7 @@ struct ZSliceFunctor // ------------------------------------------------------------------------- // 2. Precompute per-triangle Z-range for fast rejection // ------------------------------------------------------------------------- - messageHandler({IFilter::Message::Type::Info, "Preprocessing triangle data..."}); + messageHandler.sendInfoMessage("Preprocessing triangle data..."); std::vector triZRanges(numTriangles); for(usize t = 0; t < numTriangles; t++) @@ -381,7 +381,7 @@ struct ZSliceFunctor // ------------------------------------------------------------------------- // 3. Dispatch Z-slices in parallel using ParallelTaskAlgorithm // ------------------------------------------------------------------------- - messageHandler({IFilter::Message::Type::Info, fmt::format("Sampling surface mesh using scanline rasterization ({} Z-slices)...", zDim)}); + messageHandler.sendInfoMessage(fmt::format("Sampling surface mesh using scanline rasterization ({} Z-slices)...", zDim)); std::atomic_bool overflowHit(false); diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RegularizeZSpacing.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RegularizeZSpacing.cpp index 530ff475cb..444c948234 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RegularizeZSpacing.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RegularizeZSpacing.cpp @@ -115,6 +115,7 @@ RegularizeZSpacing::RegularizeZSpacing(DataStructure& dataStructure, const IFilt , m_InputValues(inputValues) , m_ShouldCancel(shouldCancel) , m_MessageHandler(msgHandler) +, m_Throttle(msgHandler) { } @@ -130,9 +131,6 @@ const std::atomic_bool& RegularizeZSpacing::getCancel() // ----------------------------------------------------------------------------- Result<> RegularizeZSpacing::operator()() { - MessageHelper messageHelper(m_MessageHandler); - ThrottledMessenger throttledMessenger = messageHelper.createThrottledMessenger(); - m_ThrottledMessengerPtr = &throttledMessenger; const auto& selectedImageGeom = m_DataStructure.getDataRefAs(m_InputValues->SelectedImageGeometryPath); auto& destImageGeom = m_DataStructure.getDataRefAs(m_InputValues->CreatedImageGeometryPath); @@ -195,7 +193,7 @@ Result<> RegularizeZSpacing::operator()() } const std::string srcName = oldDataArrayPtr->getName(); auto& newDataArray = dynamic_cast(destCellDataAM.at(srcName)); - m_MessageHandler(fmt::format("Copying Data Array: '{}' ({}/{})", srcName, arrayIndex, totalArrays)); + m_MessageHandler.sendInfoMessage(fmt::format("Copying Data Array: '{}' ({}/{})", srcName, arrayIndex, totalArrays)); ExecuteParallelFunction(oldDataArrayPtr->getDataType(), taskRunner, this, *oldDataArrayPtr, newDataArray, newToOldZPlane, sliceSize, m_ShouldCancel); } @@ -209,9 +207,6 @@ Result<> RegularizeZSpacing::operator()() void RegularizeZSpacing::sendThreadSafeProgressMessage(const std::string& message) { std::lock_guard guard(m_ProgressMessage_Mutex); - if(nullptr != m_ThrottledMessengerPtr) - { - m_ThrottledMessengerPtr->sendThrottledMessage([&]() { return message; }); - } + m_Throttle.trySendMessage(message); } } // namespace nx::core diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RegularizeZSpacing.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RegularizeZSpacing.hpp index 299d6fa61d..5f40aa1786 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RegularizeZSpacing.hpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RegularizeZSpacing.hpp @@ -5,7 +5,7 @@ #include "simplnx/DataStructure/DataPath.hpp" #include "simplnx/DataStructure/DataStructure.hpp" #include "simplnx/Filter/IFilter.hpp" -#include "simplnx/Utilities/MessageHelper.hpp" +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" #include #include @@ -52,7 +52,7 @@ class SIMPLNXCORE_EXPORT RegularizeZSpacing // Thread safe Progress Message mutable std::mutex m_ProgressMessage_Mutex; - ThrottledMessenger* m_ThrottledMessengerPtr = nullptr; + ThrottledMessageHandler m_Throttle; }; /** diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RemoveFlaggedFeatures.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RemoveFlaggedFeatures.cpp index 0549949f79..a13ab7d060 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RemoveFlaggedFeatures.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RemoveFlaggedFeatures.cpp @@ -8,17 +8,17 @@ #include "simplnx/DataStructure/Geometry/ImageGeom.hpp" #include "simplnx/Utilities/DataGroupUtilities.hpp" #include "simplnx/Utilities/MaskCompareUtilities.hpp" -#include "simplnx/Utilities/MessageHelper.hpp" #include "simplnx/Utilities/NeighborUtilities.hpp" #include "simplnx/Utilities/ParallelTaskAlgorithm.hpp" +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" using namespace nx::core; namespace { -bool IdentifyNeighbors(ImageGeom& imageGeom, Int32AbstractDataStore& featureIds, std::vector& storageArray, const std::atomic_bool& shouldCancel, MessageHelper& messageHelper) +bool IdentifyNeighbors(ImageGeom& imageGeom, Int32AbstractDataStore& featureIds, std::vector& storageArray, const std::atomic_bool& shouldCancel, const IFilter::MessageHandler& messageHandler) { - ThrottledMessenger throttledMessenger = messageHelper.createThrottledMessenger(); + ThrottledMessageHandler throttledMessenger(messageHandler); SizeVec3 uDims = imageGeom.getDimensions(); @@ -47,7 +47,7 @@ bool IdentifyNeighbors(ImageGeom& imageGeom, Int32AbstractDataStore& featureIds, if(progressCounter > progressIncrement) { - throttledMessenger.sendThrottledMessage([&]() { return fmt::format("Processing Image... {:.2f}%", CalculatePercentComplete(zIdx, dims[2])); }); + throttledMessenger.updatePercent("Processing Image", zIdx, dims[2]); progressCounter = 0; } progressCounter++; @@ -280,12 +280,10 @@ Result<> RemoveFlaggedFeatures::operator()() return {}; } - MessageHelper messageHelper(m_MessageHandler); - // Valid values Functionality::Extract and Functionality::ExtractThenRemove if(function != Functionality::Remove) { - m_MessageHandler(IFilter::ProgressMessage{IFilter::Message::Type::Info, fmt::format("Beginning Feature Extraction")}); + m_MessageHandler.sendInfoMessage(fmt::format("Beginning Feature Extraction")); { ComputeFeatureRectFilter filter; @@ -344,12 +342,12 @@ Result<> RemoveFlaggedFeatures::operator()() DataPath createdImgGeomPath({fmt::format(fmt::runtime("{}-{:0" + paddingWidth + "d}"), m_InputValues->CreatedImageGeometryPrefix, i)}); - m_MessageHandler(IFilter::ProgressMessage{IFilter::Message::Type::Info, fmt::format("Now Extracting Feature {}", i)}); + m_MessageHandler.sendInfoMessage(fmt::format("Now Extracting Feature {}", i)); taskRunner.execute(RunCropImageGeometryImpl(m_DataStructure, getCancel(), m_InputValues->ImageGeometryPath, minVoxels, maxVoxels, createdImgGeomPath)); } taskRunner.wait(); - m_MessageHandler(IFilter::ProgressMessage{IFilter::Message::Type::Info, fmt::format("All Features Successfully Extracted")}); + m_MessageHandler.sendInfoMessage(fmt::format("All Features Successfully Extracted")); } if(getCancel()) @@ -360,7 +358,7 @@ Result<> RemoveFlaggedFeatures::operator()() // Valid values Functionality::Remove and Functionality::ExtractThenRemove if(function != Functionality::Extract) { - m_MessageHandler(IFilter::ProgressMessage{IFilter::Message::Type::Info, fmt::format("Beginning Feature Removal")}); + m_MessageHandler.sendInfoMessage(fmt::format("Beginning Feature Removal")); std::vector neighbors((featureIds.getNumberOfTuples() * featureIds.getNumberOfComponents()), -1); std::vector activeObjects = FlagFeatures(featureIds, flaggedFeatures, m_InputValues->FillRemovedFeatures); @@ -381,16 +379,16 @@ Result<> RemoveFlaggedFeatures::operator()() do { count++; - m_MessageHandler(IFilter::ProgressMessage{IFilter::Message::Type::Info, fmt::format("Entering iteration number {}...", count)}); + m_MessageHandler.sendInfoMessage(fmt::format("Entering iteration number {}...", count)); std::fill(neighbors.begin(), neighbors.end(), -1); - shouldLoop = IdentifyNeighbors(imageGeom, featureIds, neighbors, getCancel(), messageHelper); + shouldLoop = IdentifyNeighbors(imageGeom, featureIds, neighbors, getCancel(), m_MessageHandler); if(getCancel()) { return {}; } - m_MessageHandler(IFilter::ProgressMessage{IFilter::Message::Type::Info, fmt::format("Filling bad voxels...")}); + m_MessageHandler.sendInfoMessage(fmt::format("Filling bad voxels...")); std::vector> voxelArrays = GenerateDataArrayList(m_DataStructure, m_InputValues->FeatureIdsArrayPath, m_InputValues->IgnoredDataArrayPaths); FindVoxelArrays(featureIds, neighbors, voxelArrays, getCancel()); } while(shouldLoop); @@ -401,7 +399,7 @@ Result<> RemoveFlaggedFeatures::operator()() return {}; } - m_MessageHandler(IFilter::ProgressMessage{IFilter::Message::Type::Info, fmt::format("Stripping excess inactive objects from model...")}); + m_MessageHandler.sendInfoMessage(fmt::format("Stripping excess inactive objects from model...")); DataPath featureGroupPath = m_InputValues->FlaggedFeaturesArrayPath.getParent(); if(!RemoveInactiveObjects(m_DataStructure, featureGroupPath, activeObjects, featureIds, flaggedFeatures->getNumberOfTuples(), m_MessageHandler, m_ShouldCancel)) { diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RemoveFlaggedVertices.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RemoveFlaggedVertices.cpp index 3a901b822f..cd4f8b348f 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RemoveFlaggedVertices.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RemoveFlaggedVertices.cpp @@ -80,7 +80,7 @@ Result<> RemoveFlaggedVertices::operator()() reducedVertexGeom.resizeVertexList(numVerticesToKeep); reducedVertexGeom.getVertexAttributeMatrix()->resizeTuples(tDims); - m_MessageHandler(nx::core::IFilter::Message{nx::core::IFilter::Message::Type::Info, fmt::format("Copying vertices to reduced geometry")}); + m_MessageHandler.sendInfoMessage(fmt::format("Copying vertices to reduced geometry")); size_t keepIndex = 0; // Loop over each vertex and only copy the vertices that were *NOT* flagged for removal @@ -107,7 +107,7 @@ Result<> RemoveFlaggedVertices::operator()() const DataPath destinationPath = reducedVertexGeom.getVertexAttributeMatrixDataPath().createChildPath(src.getName()); auto& dest = m_DataStructure.getDataRefAs(destinationPath); - m_MessageHandler(nx::core::IFilter::Message{nx::core::IFilter::Message::Type::Info, fmt::format("Copying source array '{}' to reduced geometry vertex data.", src.getName())}); + m_MessageHandler.sendInfoMessage(fmt::format("Copying source array '{}' to reduced geometry vertex data.", src.getName())); ExecuteDataFunction(RemoveFlaggedVerticesFunctor{}, src.getDataType(), src, dest, maskCompare, numVerticesToKeep); } diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReplaceElementAttributesWithNeighborValues.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReplaceElementAttributesWithNeighborValues.cpp index a18f13ea0a..e1de3d7bd2 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReplaceElementAttributesWithNeighborValues.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReplaceElementAttributesWithNeighborValues.cpp @@ -183,8 +183,8 @@ struct ExecuteTemplate if(voxelIndex > prog) { progressInt = static_cast((static_cast(voxelIndex) / totalPoints) * 100.0f); - const std::string progressMessage = fmt::format("Processing Loop({}) Progress: {}% Complete", count, progressInt); - messageHandler(IFilter::ProgressMessage{IFilter::Message::Type::Progress, progressMessage, static_cast(progressInt)}); + const std::string progressMessage = fmt::format("Processing Loop({}):", count); + messageHandler.sendProgressMessage(progressMessage, static_cast(progressInt)); prog += progIncrement; } } @@ -202,8 +202,8 @@ struct ExecuteTemplate if(voxelIndex > prog) { progressInt = static_cast((static_cast(voxelIndex) / totalPoints) * 100.0f); - const std::string progressMessage = fmt::format("Transferring Loop({}) Progress: {}% Complete", count, progressInt); - messageHandler(IFilter::ProgressMessage{IFilter::Message::Type::Progress, progressMessage, static_cast(progressInt)}); + const std::string progressMessage = fmt::format("Transferring Loop({})", count); + messageHandler.sendProgressMessage(progressMessage, static_cast(progressInt)); prog = prog + progIncrement; } diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RequireMinNumNeighbors.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RequireMinNumNeighbors.cpp index e7e3afa535..5f64e0c41e 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RequireMinNumNeighbors.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RequireMinNumNeighbors.cpp @@ -46,7 +46,7 @@ Result<> CopyTupleFromArray(DataStructure& dataStructure, const DataPath& dataAr const std::string message = fmt::format("Cannot copy into tuple index {} of array '{}'. The array contains {} tuples, the Feature Ids array contains {} tuples, and the neighbor map contains {} entries.", featureIdIndex, dataArrayPath.toString(), tupleCount, featureIdTupleCount, neighborMapSize); - mesgHandler(IFilter::Message{IFilter::Message::Type::Info, message}); + mesgHandler.sendInfoMessage(message); return MakeErrorResult(k_CopyDestinationTupleOutOfRange, message); } @@ -65,7 +65,7 @@ Result<> CopyTupleFromArray(DataStructure& dataStructure, const DataPath& dataAr { const std::string message = fmt::format("Cannot copy from tuple index {} to tuple index {} of array '{}'. The array contains {} tuples and the Feature Ids array contains {} tuples.", neighborIndex, featureIdIndex, dataArrayPath.toString(), tupleCount, featureIdTupleCount); - mesgHandler(IFilter::Message{IFilter::Message::Type::Info, message}); + mesgHandler.sendInfoMessage(message); return MakeErrorResult(k_CopySourceTupleOutOfRange, message); } @@ -191,7 +191,7 @@ Result<> RequireMinNumNeighbors::operator()() return {}; } auto numInactiveObjects = std::count(activeObjects.begin(), activeObjects.end(), false); - m_MessageHandler({nx::core::IFilter::Message::Type::Info, fmt::format("Removing {} features", numInactiveObjects)}); + m_MessageHandler.sendInfoMessage(fmt::format("Removing {} features", numInactiveObjects)); // Mark all features to be removed with a -1 value. for(usize i = 0; i < totalPoints; i++) @@ -313,7 +313,7 @@ Result<> RequireMinNumNeighbors::operator()() } std::string message = fmt::format("{} voxels to update..", counter); - m_MessageHandler(nx::core::IFilter::Message::Type::Info, message); + m_MessageHandler.sendInfoMessage(message); if(counter != 0 && !madeFill) { @@ -349,7 +349,7 @@ Result<> RequireMinNumNeighbors::operator()() } } - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Feature Count Changed: Previous: {} New: {}", totalFeatures, count)); + m_MessageHandler.sendInfoMessage(fmt::format("Feature Count Changed: Previous: {} New: {}", totalFeatures, count)); DataPath cellFeatureGroupPath = m_InputValues->NumNeighborsPath.getParent(); if(!nx::core::RemoveInactiveObjects(m_DataStructure, cellFeatureGroupPath, activeObjects, featureIds, totalFeatures, m_MessageHandler, m_ShouldCancel)) { diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RequireMinimumSizeFeatures.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RequireMinimumSizeFeatures.cpp index 41bd833d1d..628bc5fb1c 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RequireMinimumSizeFeatures.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RequireMinimumSizeFeatures.cpp @@ -3,9 +3,9 @@ #include "simplnx/DataStructure/DataArray.hpp" #include "simplnx/DataStructure/Geometry/ImageGeom.hpp" #include "simplnx/Utilities/DataGroupUtilities.hpp" -#include "simplnx/Utilities/MessageHelper.hpp" #include "simplnx/Utilities/NeighborUtilities.hpp" #include "simplnx/Utilities/ParallelTaskAlgorithm.hpp" +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" #include "simplnx/Utilities/TimeUtilities.hpp" using namespace nx::core; @@ -20,13 +20,12 @@ class RequireMinimumSizeFeaturesTransferDataImpl RequireMinimumSizeFeaturesTransferDataImpl(const RequireMinimumSizeFeaturesTransferDataImpl&) = default; RequireMinimumSizeFeaturesTransferDataImpl(RequireMinimumSizeFeatures* filterAlg, usize totalPoints, const Int32AbstractDataStore& featureIds, const std::vector& neighborVoxelIndex, - const std::shared_ptr& dataArrayPtr, MessageHelper& messageHelper, const std::atomic_bool& shouldCancel) + const std::shared_ptr& dataArrayPtr, const std::atomic_bool& shouldCancel) : m_FilterAlg(filterAlg) , m_TotalPoints(totalPoints) , m_NeighborsVoxelIndex(neighborVoxelIndex) , m_DataArrayPtr(dataArrayPtr) , m_FeatureIds(featureIds) - , m_MessageHelper(messageHelper) , m_ShouldCancel(shouldCancel) { } @@ -38,14 +37,13 @@ class RequireMinimumSizeFeaturesTransferDataImpl void operator()() const { - ThrottledMessenger throttledMessenger = m_MessageHelper.createThrottledMessenger(); std::string arrayName = m_DataArrayPtr->getName(); usize prog = std::max(m_TotalPoints / 100ULL, 1ULL); for(usize voxelIndex = 0; voxelIndex < m_TotalPoints; voxelIndex++) { if(voxelIndex % prog == 0) { - throttledMessenger.sendThrottledMessage([&]() { return fmt::format("Processing {}: {:.2f}% completed", arrayName, CalculatePercentComplete(voxelIndex, m_TotalPoints)); }); + m_FilterAlg->sendThreadSafeProgressMessage(fmt::format("Processing {}: {:.2f}% completed", arrayName, CalculatePercentComplete(voxelIndex, m_TotalPoints))); } if(m_ShouldCancel) { @@ -70,7 +68,6 @@ class RequireMinimumSizeFeaturesTransferDataImpl std::vector m_NeighborsVoxelIndex; const std::shared_ptr m_DataArrayPtr; const Int32AbstractDataStore& m_FeatureIds; - MessageHelper& m_MessageHelper; const std::atomic_bool& m_ShouldCancel; }; @@ -83,12 +80,20 @@ RequireMinimumSizeFeatures::RequireMinimumSizeFeatures(DataStructure& dataStruct , m_InputValues(inputValues) , m_ShouldCancel(shouldCancel) , m_MessageHandler(mesgHandler) +, m_Throttle(mesgHandler) { } // ----------------------------------------------------------------------------- RequireMinimumSizeFeatures::~RequireMinimumSizeFeatures() noexcept = default; +// ----------------------------------------------------------------------------- +void RequireMinimumSizeFeatures::sendThreadSafeProgressMessage(const std::string& message) +{ + std::lock_guard guard(m_ProgressMessage_Mutex); + m_Throttle.trySendMessage(message); +} + // ----------------------------------------------------------------------------- Result<> RequireMinimumSizeFeatures::operator()() { @@ -151,7 +156,7 @@ Result<> RequireMinimumSizeFeatures::operator()() } } std::string message = fmt::format("Feature Count Changed: Previous: {} New: {}", currentFeatureCount, count); - m_MessageHandler(nx::core::IFilter::Message{nx::core::IFilter::Message::Type::Info, message}); + m_MessageHandler.sendInfoMessage(message); nx::core::RemoveInactiveObjects(m_DataStructure, cellFeatureGroupPath, activeObjects, featureIdsStoreRef, currentFeatureCount, m_MessageHandler, m_ShouldCancel); @@ -160,8 +165,7 @@ Result<> RequireMinimumSizeFeatures::operator()() void RequireMinimumSizeFeatures::assignBadVoxels(SizeVec3 dimensions, const Int32AbstractDataStore& featureNumCellsStoreRef) { - MessageHelper messageHelper(m_MessageHandler); - messageHelper.sendMessage(fmt::format("Assigning voxels....")); + m_MessageHandler.sendInfoMessage(fmt::format("Assigning voxels....")); Int32AbstractDataStore& featureIds = m_DataStructure.getDataAs(m_InputValues->FeatureIdsPath)->getDataStoreRef(); usize totalPoints = featureIds.getNumberOfTuples(); @@ -243,7 +247,7 @@ void RequireMinimumSizeFeatures::assignBadVoxels(SizeVec3 dimensions, const Int3 } } - messageHelper.sendMessage(fmt::format("Remaining voxels: {} - Updating Data Arrays... ", counter)); + m_MessageHandler.sendInfoMessage(fmt::format("Remaining voxels: {} - Updating Data Arrays... ", counter)); // Build up a list of the DataArrays that we are going to operate on. const std::vector> voxelArrays = nx::core::GenerateDataArrayList(m_DataStructure, m_InputValues->FeatureIdsPath, {}); @@ -259,13 +263,13 @@ void RequireMinimumSizeFeatures::assignBadVoxels(SizeVec3 dimensions, const Int3 continue; } - taskRunner.execute(RequireMinimumSizeFeaturesTransferDataImpl(this, totalPoints, featureIds, neighborsVoxelIndex, voxelArray, messageHelper, m_ShouldCancel)); + taskRunner.execute(RequireMinimumSizeFeaturesTransferDataImpl(this, totalPoints, featureIds, neighborsVoxelIndex, voxelArray, m_ShouldCancel)); } taskRunner.wait(); // This will spill over if the number of DataArrays to process does not divide evenly by the number of threads. // Now update the feature Ids auto featureIDataArray = m_DataStructure.getSharedDataAs(m_InputValues->FeatureIdsPath); taskRunner.setParallelizationEnabled(false); // Do this to make the next call synchronous - taskRunner.execute(RequireMinimumSizeFeaturesTransferDataImpl(this, totalPoints, featureIds, neighborsVoxelIndex, featureIDataArray, messageHelper, m_ShouldCancel)); + taskRunner.execute(RequireMinimumSizeFeaturesTransferDataImpl(this, totalPoints, featureIds, neighborsVoxelIndex, featureIDataArray, m_ShouldCancel)); } } @@ -274,8 +278,7 @@ std::vector RequireMinimumSizeFeatures::removeSmallFeatures(Int32AbstractD const Int32AbstractDataStore* featurePhases, int32 phaseNumber, bool applyToSinglePhase, int64 minAllowedFeatureSize, Error& errorReturn) { - MessageHelper messageHelper(m_MessageHandler); - messageHelper.sendMessage(fmt::format("Removing small features....")); + m_MessageHandler.sendInfoMessage(fmt::format("Removing small features....")); usize totalPoints = featureIdsStoreRef.getNumberOfTuples(); diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RequireMinimumSizeFeatures.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RequireMinimumSizeFeatures.hpp index f1eea50e9f..af94597be1 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RequireMinimumSizeFeatures.hpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RequireMinimumSizeFeatures.hpp @@ -4,10 +4,13 @@ #include "simplnx/DataStructure/DataPath.hpp" #include "simplnx/Filter/IFilter.hpp" +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" + #include "simplnx/Parameters/ArraySelectionParameter.hpp" #include "simplnx/Parameters/BoolParameter.hpp" #include "simplnx/Parameters/GeometrySelectionParameter.hpp" #include "simplnx/Parameters/NumberParameter.hpp" +#include namespace nx::core { @@ -41,6 +44,12 @@ class SIMPLNXCORE_EXPORT RequireMinimumSizeFeatures Result<> operator()(); + /** + * @brief Thread-safe progress update. Safe to call from the parallel per-array workers. + * @param message Fully rendered progress text for one array + */ + void sendThreadSafeProgressMessage(const std::string& message); + protected: /** * @@ -68,6 +77,8 @@ class SIMPLNXCORE_EXPORT RequireMinimumSizeFeatures const RequireMinimumSizeFeaturesInputValues* m_InputValues = nullptr; const std::atomic_bool& m_ShouldCancel; const IFilter::MessageHandler& m_MessageHandler; + mutable std::mutex m_ProgressMessage_Mutex; + ThrottledMessageHandler m_Throttle; }; } // namespace nx::core diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ResampleImageGeom.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ResampleImageGeom.cpp index f651009a4e..35d53a4e76 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ResampleImageGeom.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ResampleImageGeom.cpp @@ -4,7 +4,6 @@ #include "simplnx/DataStructure/Geometry/ImageGeom.hpp" #include "simplnx/DataStructure/StringArray.hpp" #include "simplnx/Utilities/DataArrayUtilities.hpp" -#include "simplnx/Utilities/MessageHelper.hpp" #include "simplnx/Utilities/ParallelAlgorithmUtilities.hpp" #include "simplnx/Utilities/ParallelDataAlgorithm.hpp" #include "simplnx/Utilities/ParallelTaskAlgorithm.hpp" @@ -92,6 +91,7 @@ ResampleImageGeom::ResampleImageGeom(DataStructure& dataStructure, const IFilter , m_InputValues(inputValues) , m_ShouldCancel(shouldCancel) , m_MessageHandler(msgHandler) +, m_Throttle(msgHandler) { } @@ -107,9 +107,6 @@ const std::atomic_bool& ResampleImageGeom::getCancel() // ----------------------------------------------------------------------------- Result<> ResampleImageGeom::operator()() { - MessageHelper messageHelper(m_MessageHandler); - ThrottledMessenger throttledMessenger = messageHelper.createThrottledMessenger(); - m_ThrottledMessengerPtr = &throttledMessenger; const auto& selectedImageGeom = m_DataStructure.getDataRefAs(m_InputValues->SelectedImageGeometryPath); @@ -134,7 +131,7 @@ Result<> ResampleImageGeom::operator()() const auto& oldDataArray = dynamic_cast(*oldDataObject); const std::string srcName = oldDataArray.getName(); auto& newDataArray = dynamic_cast(destCellDataAM.at(srcName)); - m_MessageHandler(fmt::format("Resampling Data Array: '{}' ({}/{})", srcName, arrayIndex, totalArrays)); + m_MessageHandler.sendInfoMessage(fmt::format("Resampling Data Array: '{}' ({}/{})", srcName, arrayIndex, totalArrays)); ExecuteParallelFunction(oldDataArray.getDataType(), taskRunner, this, oldDataArray, newDataArray, selectedImageGeom, destImageGeom, m_ShouldCancel); } @@ -213,8 +210,5 @@ Result<> ResampleImageGeom::operator()() void ResampleImageGeom::sendThreadSafeProgressMessage(const std::string& message) { std::lock_guard guard(m_ProgressMessage_Mutex); - if(nullptr != m_ThrottledMessengerPtr) - { - m_ThrottledMessengerPtr->sendThrottledMessage([&]() { return message; }); - } + m_Throttle.trySendMessage(message); } diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ResampleImageGeom.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ResampleImageGeom.hpp index 167693bc24..d4cacc2be8 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ResampleImageGeom.hpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ResampleImageGeom.hpp @@ -6,7 +6,7 @@ #include "simplnx/DataStructure/DataStructure.hpp" #include "simplnx/Filter/IFilter.hpp" #include "simplnx/Parameters/ChoicesParameter.hpp" -#include "simplnx/Utilities/MessageHelper.hpp" +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" #include @@ -54,7 +54,7 @@ class SIMPLNXCORE_EXPORT ResampleImageGeom // Thread safe Progress Message mutable std::mutex m_ProgressMessage_Mutex; - ThrottledMessenger* m_ThrottledMessengerPtr = nullptr; + ThrottledMessageHandler m_Throttle; }; } // namespace nx::core diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ResampleRectGridToImageGeom.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ResampleRectGridToImageGeom.cpp index 8343cbc891..ad3c1f10aa 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ResampleRectGridToImageGeom.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ResampleRectGridToImageGeom.cpp @@ -70,7 +70,7 @@ Result<> ResampleRectGridToImageGeom::operator()() const auto& srcArray = m_DataStructure.getDataRefAs(srcArrayPath); const std::string srcName = srcArray.getName(); auto& destDataArray = dynamic_cast(destCellDataAM.at(srcName)); - m_MessageHandler(fmt::format("Resample Rect Grid To Image Geom || Copying Data Array {}", srcName)); + m_MessageHandler.sendInfoMessage(fmt::format("Resample Rect Grid To Image Geom || Copying Data Array {}", srcName)); CopyFromArray::RunParallelMapRectToImage(destDataArray, taskRunner, srcArray, origin, imageGeomDims, imageGeomSpacing, rectGridDims, xGridValues, yGridValues, zGridValues); } diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReshapeDataArray.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReshapeDataArray.hpp index 01627a64bb..60a3b7b40d 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReshapeDataArray.hpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ReshapeDataArray.hpp @@ -34,8 +34,8 @@ Result<> ReshapeArrayImpl(DataStructure& dataStructure, const DataPath& inputArr { auto& inputDataArray = dataStructure.getDataRefAs(inputArrayPath); auto& outputDataArray = dataStructure.getDataRefAs(outputArrayPath); - messageHandler({IFilter::Message::Type::Info, fmt::format("Reshaping data array '{}' from [{}] to [{}]...", inputArrayPath.toString(), fmt::join(inputDataArray.getTupleShape(), ","), - fmt::join(outputDataArray.getTupleShape(), ","))}); + messageHandler.sendInfoMessage( + fmt::format("Reshaping data array '{}' from [{}] to [{}]...", inputArrayPath.toString(), fmt::join(inputDataArray.getTupleShape(), ","), fmt::join(outputDataArray.getTupleShape(), ","))); usize tuplesToCopy = std::min(inputDataArray.getNumberOfTuples(), outputDataArray.getNumberOfTuples()); auto result = CopyFromArray::CopyData(inputDataArray, outputDataArray, 0, 0, tuplesToCopy); if(result.invalid()) @@ -52,8 +52,8 @@ Result<> ReshapeNeighborListImpl(DataStructure& dataStructure, const DataPath& i const auto& inputNeighborList = dataStructure.getDataRefAs>(inputArrayPath); auto& outputNeighborList = dataStructure.getDataRefAs>(outputArrayPath); - messageHandler({IFilter::Message::Type::Info, fmt::format("Reshaping neighbor list '{}' from [{}] to [{}]...", inputArrayPath.toString(), fmt::join(inputNeighborList.getTupleShape(), ","), - fmt::join(outputNeighborList.getTupleShape(), ","))}); + messageHandler.sendInfoMessage(fmt::format("Reshaping neighbor list '{}' from [{}] to [{}]...", inputArrayPath.toString(), fmt::join(inputNeighborList.getTupleShape(), ","), + fmt::join(outputNeighborList.getTupleShape(), ","))); for(int32 listIdx = 0; listIdx < outputNeighborList.getNumberOfTuples(); ++listIdx) { if(listIdx < inputNeighborList.getNumberOfTuples()) diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RotateSampleRefFrame.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RotateSampleRefFrame.cpp index bba613c54c..f06dcd19cb 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RotateSampleRefFrame.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RotateSampleRefFrame.cpp @@ -33,12 +33,6 @@ RotateSampleRefFrame::RotateSampleRefFrame(DataStructure& dataStructure, const I // ----------------------------------------------------------------------------- RotateSampleRefFrame::~RotateSampleRefFrame() noexcept = default; -// ----------------------------------------------------------------------------- -void RotateSampleRefFrame::updateProgress(const std::string& message) -{ - m_MessageHandler(IFilter::Message::Type::Info, message); -} - // ----------------------------------------------------------------------------- const std::atomic_bool& RotateSampleRefFrame::getCancel() { @@ -87,7 +81,7 @@ Result<> RotateSampleRefFrame::operator()() const auto* srcDataArray = m_DataStructure.getDataAs(srcCellDataAMPath.createChildPath(srcDataObject->getName())); auto* destDataArray = m_DataStructure.getDataAs(destCellDataAMPath.createChildPath(srcDataObject->getName())); - m_MessageHandler(fmt::format("Rotating Volume || Copying Data Array {}", srcDataObject->getName())); + m_MessageHandler.sendInfoMessage(fmt::format("Rotating Volume || Copying Data Array {}", srcDataObject->getName())); ExecuteParallelFunction(srcDataArray->getDataType(), taskRunner, srcDataArray, destDataArray, rotateArgs, rotationMatrix, m_InputValues->SliceBySlice, &filterProgressCallback); diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RotateSampleRefFrame.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RotateSampleRefFrame.hpp index 33432a5401..7232c1979c 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RotateSampleRefFrame.hpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RotateSampleRefFrame.hpp @@ -44,7 +44,6 @@ class SIMPLNXCORE_EXPORT RotateSampleRefFrame }; Result<> operator()(); - void updateProgress(const std::string& message); const std::atomic_bool& getCancel(); private: diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/Silhouette.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/Silhouette.cpp index 5b61ed32e3..e9310584c9 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/Silhouette.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/Silhouette.cpp @@ -143,12 +143,6 @@ Silhouette::Silhouette(DataStructure& dataStructure, const IFilter::MessageHandl // ----------------------------------------------------------------------------- Silhouette::~Silhouette() noexcept = default; -// ----------------------------------------------------------------------------- -void Silhouette::updateProgress(const std::string& message) -{ - m_MessageHandler(IFilter::Message::Type::Info, message); -} - // ----------------------------------------------------------------------------- const std::atomic_bool& Silhouette::getCancel() { diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/Silhouette.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/Silhouette.hpp index e7c62f0b99..33749b0122 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/Silhouette.hpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/Silhouette.hpp @@ -36,7 +36,6 @@ class SIMPLNXCORE_EXPORT Silhouette Silhouette& operator=(Silhouette&&) noexcept = delete; Result<> operator()(); - void updateProgress(const std::string& message); const std::atomic_bool& getCancel(); private: diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/SplitDataArrayByTuple.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/SplitDataArrayByTuple.cpp index 58fa59fbeb..8cfcf3f176 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/SplitDataArrayByTuple.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/SplitDataArrayByTuple.cpp @@ -123,7 +123,7 @@ typename std::enable_if::value, Result<>>::type auto& outputArray = dataStructure.getDataRefAs(outputArrayPaths[i]); - messageHandler({IFilter::Message::Type::Info, fmt::format("Splitting data array '{}' by tuple ({}/{})", inputArrayPath.toString(), i + 1, outputArrayPaths.size())}); + messageHandler.sendInfoMessage(fmt::format("Splitting data array '{}' by tuple ({}/{})", inputArrayPath.toString(), i + 1, outputArrayPaths.size())); // Run this directly since ArrayType is the template parameter taskRunner.execute(SplitDataArrayByTupleImpl(inputArray, outputArray, inputTupleShapeOffset, shouldCancel)); @@ -150,7 +150,7 @@ Result<> SplitNeighborListsByTupleImpl(DataStructure& dataStructure, const DataP return {}; } - messageHandler({IFilter::Message::Type::Info, fmt::format("Splitting neighbor list '{}' by tuple ({}/{})", inputArrayPath.toString(), i + 1, outputArrayPaths.size())}); + messageHandler.sendInfoMessage(fmt::format("Splitting neighbor list '{}' by tuple ({}/{})", inputArrayPath.toString(), i + 1, outputArrayPaths.size())); auto& outputNeighborList = dataStructure.getDataRefAs>(outputArrayPaths[i]); taskRunner.execute(SplitNeighborListByTupleImpl(inputNeighborList, outputNeighborList, inputTupleOffset, shouldCancel)); diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/SurfaceNets.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/SurfaceNets.cpp index ac95808ad2..b8dae0d931 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/SurfaceNets.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/SurfaceNets.cpp @@ -428,7 +428,7 @@ Result<> SurfaceNets::operator()() if(m_InputValues->RepairTriangleWinding) { // Generate Connectivity - m_MessageHandler("Generating Connectivity and Triangle Neighbors..."); + m_MessageHandler.sendInfoMessage("Generating Connectivity and Triangle Neighbors..."); triangleGeom.findElementNeighbors(true); const auto optionalId = triangleGeom.getElementNeighborsId(); if(!optionalId.has_value()) @@ -437,7 +437,7 @@ Result<> SurfaceNets::operator()() } const auto& connectivity = m_DataStructure.getDataRefAs(optionalId.value()); - m_MessageHandler("Repairing Windings..."); + m_MessageHandler.sendInfoMessage("Repairing Windings..."); windingResult = MeshingUtilities::RepairTriangleWinding(triangleGeom.getFaces()->getDataStoreRef(), connectivity, m_DataStructure.getDataAs(m_InputValues->FaceLabelsDataPath)->getDataStoreRef(), m_ShouldCancel, m_MessageHandler); diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/VerifyTriangleWinding.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/VerifyTriangleWinding.cpp index 63f6207847..704a77007f 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/VerifyTriangleWinding.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/VerifyTriangleWinding.cpp @@ -129,10 +129,10 @@ Result<> VerifyTriangleWinding::operator()() return result; } - m_MessageHandler("Checking for duplicates - sorting vertices"); + m_MessageHandler.sendInfoMessage("Checking for duplicates - sorting vertices"); const MeshingUtilities::SortedVerticesList sortedVerticesList = MeshingUtilities::OrderSharedVertices(m_DataStructure.getDataRefAs(m_InputValues->TargetGeometryPath), m_ShouldCancel); - m_MessageHandler("Checking for duplicates - validating"); + m_MessageHandler.sendInfoMessage("Checking for duplicates - validating"); auto& triGeom = m_DataStructure.getDataRefAs(m_InputValues->TargetGeometryPath); if(MeshingUtilities::HasDuplicateVertices(triGeom.getVertices()->getDataStoreRef(), sortedVerticesList)) { @@ -149,7 +149,7 @@ Result<> VerifyTriangleWinding::operator()() Result<> windingResult = {}; { // Generate Connectivity - m_MessageHandler("Generating Connectivity and Triangle Neighbors..."); + m_MessageHandler.sendInfoMessage("Generating Connectivity and Triangle Neighbors..."); triGeom.findElementNeighbors(true); const auto optionalId = triGeom.getElementNeighborsId(); if(!optionalId.has_value()) @@ -158,7 +158,7 @@ Result<> VerifyTriangleWinding::operator()() } const auto& connectivity = m_DataStructure.getDataRefAs(optionalId.value()); - m_MessageHandler("Repairing Windings..."); + m_MessageHandler.sendInfoMessage("Repairing Windings..."); // This is reused since it may contain warnings windingResult = MeshingUtilities::RepairTriangleWinding(triangles, connectivity, idsStore, m_ShouldCancel, m_MessageHandler); if(windingResult.invalid()) @@ -171,7 +171,7 @@ Result<> VerifyTriangleWinding::operator()() m_DataStructure.removeData(triGeom.getElementNeighborsId().value()); } - m_MessageHandler("Assessing reversal - calculating feature volumes"); + m_MessageHandler.sendInfoMessage("Assessing reversal - calculating feature volumes"); // Get max group (feature id != 0) int32 maxFeature = 0; for(int32 i = 0; i < idsStore.getSize(); i++) @@ -186,7 +186,7 @@ Result<> VerifyTriangleWinding::operator()() return volumeResult; } - m_MessageHandler("Implementing reversal in parallel"); + m_MessageHandler.sendInfoMessage("Implementing reversal in parallel"); std::vector reversalTargets = {}; reversalTargets.reserve(volumes.size()); for(usize i = 1; i < volumes.size(); i++) // zero is an invalid feature label @@ -208,7 +208,7 @@ Result<> VerifyTriangleWinding::operator()() if(m_InputValues->RepairNormals) { - m_MessageHandler("Recalculating normals"); + m_MessageHandler.sendInfoMessage("Recalculating normals"); auto& normals = m_DataStructure.getDataAs(m_InputValues->TriangleNormalsPath)->getDataStoreRef(); // Parallel algorithm to calculate normals diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/WriteAbaqusHexahedron.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/WriteAbaqusHexahedron.cpp index fd1c876a1e..fde6d78034 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/WriteAbaqusHexahedron.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/WriteAbaqusHexahedron.cpp @@ -357,7 +357,7 @@ const std::atomic_bool& WriteAbaqusHexahedron::getCancel() // ----------------------------------------------------------------------------- void WriteAbaqusHexahedron::sendMessage(const std::string& message) { - m_MessageHandler(IFilter::Message::Type::Info, message); + m_MessageHandler.sendInfoMessage(message); } // ----------------------------------------------------------------------------- @@ -397,7 +397,7 @@ Result<> WriteAbaqusHexahedron::operator()() DeleteFiles(fileList); // delete files return {}; } - m_MessageHandler(IFilter::Message::Type::Info, "Writing Sections (File 1/5) Complete"); + m_MessageHandler.sendInfoMessage("Writing Sections (File 1/5) Complete"); err = writeElems(this, fileList[1].value().tempFilePath().string(), cDims.data(), pDims, getCancel()); // Elements file if(err < 0) @@ -409,7 +409,7 @@ Result<> WriteAbaqusHexahedron::operator()() DeleteFiles(fileList); // delete files return {}; } - m_MessageHandler(IFilter::Message::Type::Info, "Writing Sections (File 2/5) Complete"); + m_MessageHandler.sendInfoMessage("Writing Sections (File 2/5) Complete"); err = writeSects(fileList[2].value().tempFilePath().string(), featureIds, m_InputValues->HourglassStiffness); // Sections file if(err < 0) @@ -421,7 +421,7 @@ Result<> WriteAbaqusHexahedron::operator()() DeleteFiles(fileList); // delete files return {}; } - m_MessageHandler(IFilter::Message::Type::Info, "Writing Sections (File 3/5) Complete"); + m_MessageHandler.sendInfoMessage("Writing Sections (File 3/5) Complete"); err = writeElset(this, fileList[3].value().tempFilePath().string(), totalPoints, featureIds, getCancel()); // Element set file if(err < 0) @@ -433,7 +433,7 @@ Result<> WriteAbaqusHexahedron::operator()() DeleteFiles(fileList); // delete files return {}; } - m_MessageHandler(IFilter::Message::Type::Info, "Writing Sections (File 4/5) Complete"); + m_MessageHandler.sendInfoMessage("Writing Sections (File 4/5) Complete"); err = writeMaster(fileList[4].value().tempFilePath().string(), m_InputValues->JobName, m_InputValues->FilePrefix); // Master file if(err < 0) @@ -445,7 +445,7 @@ Result<> WriteAbaqusHexahedron::operator()() DeleteFiles(fileList); // delete files return {}; } - m_MessageHandler(IFilter::Message::Type::Info, "Writing Sections (File 5/5) Complete"); + m_MessageHandler.sendInfoMessage("Writing Sections (File 5/5) Complete"); for(auto& file : fileList) { diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/WriteImage.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/WriteImage.cpp index 6edaf1d1fb..992541d3d5 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/WriteImage.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/WriteImage.cpp @@ -360,7 +360,7 @@ Result<> WriteImage::operator()() // Shared per-slice writer: names the file, writes via the ImageIO layer, commits atomically. auto writeSlice = [&](std::vector& sliceBuffer, usize slice) -> Result<> { - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Writing slice {}/{}", slice + 1, sliceCount)); + m_MessageHandler.sendInfoMessage(fmt::format("Writing slice {}/{}", slice + 1, sliceCount)); // A single-slice volume writes exactly the user-specified file name; the index suffix is only // appended when multiple slices are produced. fs::path slicePath = parent / fmt::format("{}{}", stem.string(), ext.string()); diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/WriteLAMMPSFile.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/WriteLAMMPSFile.cpp index b74242c33f..70ff664c51 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/WriteLAMMPSFile.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/WriteLAMMPSFile.cpp @@ -3,7 +3,7 @@ #include "simplnx/DataStructure/DataArray.hpp" #include "simplnx/DataStructure/Geometry/VertexGeom.hpp" #include "simplnx/Utilities/FilterUtilities.hpp" -#include "simplnx/Utilities/MessageHelper.hpp" +#include "simplnx/Utilities/ThrottledMessageHandler.hpp" #include @@ -55,9 +55,7 @@ Result<> WriteLAMMPSFile::operator()() const Int32AbstractDataStore& atomLabels = m_DataStructure.getDataAs(m_InputValues->AtomLabelsPath)->getDataStoreRef(); - MessageHelper messageHelper(m_MessageHandler); - - messageHelper.sendMessage("Finding Max Atom Label..."); + m_MessageHandler.sendInfoMessage("Finding Max Atom Label..."); int32 atomTypes = 0; for(usize i = 0; i < atomLabels.getNumberOfTuples(); i++) { @@ -80,7 +78,7 @@ Result<> WriteLAMMPSFile::operator()() float zMax = 0.0; int dummy = 0; - messageHelper.sendMessage("Finding Min/Max Vertices..."); + m_MessageHandler.sendInfoMessage("Finding Min/Max Vertices..."); for(usize i = 0; i < verts.getNumberOfTuples(); i++) { if(m_ShouldCancel) @@ -114,7 +112,7 @@ Result<> WriteLAMMPSFile::operator()() } } - messageHelper.sendMessage("Writing File Metadata..."); + m_MessageHandler.sendInfoMessage("Writing File Metadata..."); file << "LAMMPS data file\n"; file << "\n"; file << fmt::format("{} atoms\n", static_cast(verts.getNumberOfTuples())); @@ -128,8 +126,8 @@ Result<> WriteLAMMPSFile::operator()() file << "Atoms\n"; file << "\n"; - messageHelper.sendMessage("Exporting Data..."); - ThrottledMessenger throttledMessenger = messageHelper.createThrottledMessenger(); + m_MessageHandler.sendInfoMessage("Exporting Data..."); + ThrottledMessageHandler throttledMessenger(m_MessageHandler); // Write the Atom positions (Vertices) usize numVerts = verts.getNumberOfTuples(); usize increment = numVerts / 1000; @@ -141,7 +139,7 @@ Result<> WriteLAMMPSFile::operator()() } if(i % increment == 0) { - throttledMessenger.sendThrottledMessage([&]() { return fmt::format("Exporting Data {:.2f}% completed", CalculatePercentComplete(i, numVerts)); }); + throttledMessenger.updatePercent("Exporting Data", i, numVerts); } // Write the positions to the output file file << fmt::format("{} {:d} {:f} {:f} {:f} {:d} {:d} {:d}\n", i + 1LL, atomLabels.getValue(i), verts.getValue((i * 3) + 0), verts.getValue((i * 3) + 1), verts.getValue((i * 3) + 2), dummy, dummy, diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/WriteLosAlamosFFT.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/WriteLosAlamosFFT.cpp index 05e9c8ee5e..675d64b4af 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/WriteLosAlamosFFT.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/WriteLosAlamosFFT.cpp @@ -73,7 +73,7 @@ Result<> WriteLosAlamosFFT::operator()() usize index = (z * dims[0] * dims[1]) + (dims[0] * y) + x; if(std::chrono::duration_cast(std::chrono::steady_clock::now() - start).count() > 1000) { - m_MessageHandler(fmt::format("Writing in progress: {} of {} written. Percentage: {}%", index, total, static_cast((static_cast(index) / total) * 100.0))); + m_MessageHandler.sendInfoMessage(fmt::format("Writing in progress: {} of {} written. Percentage: {}%", index, total, static_cast((static_cast(index) / total) * 100.0))); start = std::chrono::steady_clock::now(); } diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/WriteNodesAndElementsFiles.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/WriteNodesAndElementsFiles.cpp index bd0988c34d..d8594f156a 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/WriteNodesAndElementsFiles.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/WriteNodesAndElementsFiles.cpp @@ -102,7 +102,7 @@ const std::atomic_bool& WriteNodesAndElementsFiles::getCancel() // ----------------------------------------------------------------------------- void WriteNodesAndElementsFiles::sendMessage(const std::string& message) { - m_MessageHandler(IFilter::Message::Type::Info, message); + m_MessageHandler.sendInfoMessage(message); } // ----------------------------------------------------------------------------- diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/WriteSPParksSites.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/WriteSPParksSites.cpp index 7ab44d5495..b27ec834dd 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/WriteSPParksSites.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/WriteSPParksSites.cpp @@ -63,8 +63,7 @@ Result<> WriteFile(const DataStructure& dataStructure, const WriteSPParksSitesIn if(std::chrono::duration_cast(now - start).count() > 1000) { const int32 progInt = static_cast((static_cast(k) / totalpoints) * 100.0f); - std::string message = fmt::format("Writing File {}%", progInt); - messageHandler(nx::core::IFilter::ProgressMessage{nx::core::IFilter::Message::Type::Info, message, progInt}); + messageHandler.sendProgressMessage("Writing File", progInt); start = std::chrono::steady_clock::now(); } if(shouldCancel) diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/WriteStlFile.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/WriteStlFile.cpp index e7c57d0954..185c025b8a 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/WriteStlFile.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/WriteStlFile.cpp @@ -572,7 +572,7 @@ Result<> WriteStlFile::operator()() } fileList.emplace_back(std::move(atomicFileResult.value())); - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Writing STL for Feature Id {}", featureId)); + m_MessageHandler.sendInfoMessage(fmt::format("Writing STL for Feature Id {}", featureId)); taskRunner.execute(MultiWriteStlFileImpl(this, fileList[fileIndex], nTriangles, {"DREAM3D Generated For Feature ID " + StringUtilities::number(featureId)}, triangles, vertices, featureIds, featureId, m_InputValues->HIDDEN_MaxTrianglesPerFile, m_ShouldCancel)); fileIndex++; @@ -610,7 +610,7 @@ Result<> WriteStlFile::operator()() } fileList.emplace_back(std::move(atomicFileResult.value())); - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Writing STL for Phase {} and Feature Id {}", value, featureId)); + m_MessageHandler.sendInfoMessage(fmt::format("Writing STL for Phase {} and Feature Id {}", value, featureId)); taskRunner.execute(MultiWriteStlFileImpl(this, fileList[fileIndex], nTriangles, {"DREAM3D Generated For Feature ID " + StringUtilities::number(featureId) + " Phase " + StringUtilities::number(value)}, triangles, vertices, featureIds, featureId, m_InputValues->HIDDEN_MaxTrianglesPerFile, m_ShouldCancel)); @@ -645,7 +645,7 @@ Result<> WriteStlFile::operator()() } fileList.emplace_back(std::move(atomicFileResult.value())); - m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Writing STL for Part Number {}", currentPartNumber)); + m_MessageHandler.sendInfoMessage(fmt::format("Writing STL for Part Number {}", currentPartNumber)); taskRunner.execute(MultiWriteStlFileImpl(this, fileList[fileIndex], nTriangles, {"DREAM3D Generated For Part Number " + StringUtilities::number(currentPartNumber)}, triangles, vertices, partNumbers, currentPartNumber, m_InputValues->HIDDEN_MaxTrianglesPerFile, m_ShouldCancel)); fileIndex++; diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/WriteVtkStructuredPoints.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/WriteVtkStructuredPoints.cpp index cec378e608..d3c4acd339 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/WriteVtkStructuredPoints.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/WriteVtkStructuredPoints.cpp @@ -65,7 +65,7 @@ Result<> WriteVtkStructuredPoints::operator()() Result<> result; for(const auto& arrayPath : m_InputValues->SelectedDataArrayPaths) { - m_MessageHandler({nx::core::IFilter::Message::Type::Info, fmt::format("Writing {}", arrayPath.toString())}); + m_MessageHandler.sendInfoMessage(fmt::format("Writing {}", arrayPath.toString())); auto& dataArray = m_DataStructure.getDataRefAs(arrayPath); result = MergeResults(result, ExecuteNeighborFunction(WriteVtkDataFunctor{}, dataArray.getDataType(), outStrm, dataArray, m_InputValues->WriteBinaryFile, m_MessageHandler, m_ShouldCancel)); } diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/IterativeClosestPointFilter.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/IterativeClosestPointFilter.cpp index 3815543bd3..22a15d72a9 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/IterativeClosestPointFilter.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/IterativeClosestPointFilter.cpp @@ -11,7 +11,6 @@ #include "simplnx/Parameters/GeometrySelectionParameter.hpp" #include "simplnx/Parameters/NumberParameter.hpp" #include "simplnx/Utilities/FilterUtilities.hpp" -#include "simplnx/Utilities/MessageHelper.hpp" #include "simplnx/Utilities/SIMPLConversion.hpp" namespace nx::core diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/MapPointCloudToRegularGridFilter.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/MapPointCloudToRegularGridFilter.cpp index bb57e5683c..fc184a7647 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/MapPointCloudToRegularGridFilter.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/MapPointCloudToRegularGridFilter.cpp @@ -374,7 +374,7 @@ Result<> MapPointCloudToRegularGridFilter::executeImpl(DataStructure& dataStruct if(samplingGridType == 0) { // Create the regular grid - messageHandler("Creating Regular Grid"); + messageHandler.sendInfoMessage("Creating Regular Grid"); auto result = CreateRegularGrid(dataStructure, filterArgs); if(result.invalid()) { diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/VtkUtilities.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/VtkUtilities.hpp index 6270f4c036..e702162a1a 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/VtkUtilities.hpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/VtkUtilities.hpp @@ -122,14 +122,12 @@ std::string TypeForPrimitive(const IFilter::MessageHandler& messageHandler) return "char"; } - messageHandler(IFilter::Message::Type::Info, fmt::format("Error: TypeForPrimitive - Unknown Type: ", typeid(T).name())); + messageHandler.sendInfoMessage(fmt::format("Error: TypeForPrimitive - Unknown Type: ", typeid(T).name())); if(const char* name = typeid(T).name(); nullptr != name && name[0] == 'l') { - messageHandler( - IFilter::Message::Type::Info, - fmt::format( - "You are using 'long int' as a type which is not 32/64 bit safe. It is suggested you use one of the H5SupportTypes defined in such as int32_t or uint32_t.", - typeid(T).name())); + messageHandler.sendInfoMessage(fmt::format( + "You are using 'long int' as a type which is not 32/64 bit safe. It is suggested you use one of the H5SupportTypes defined in such as int32_t or uint32_t.", + typeid(T).name())); } return ""; } @@ -143,7 +141,7 @@ struct WriteVtkDataArrayFunctor auto* dataArray = dataStructure.getDataAs>(arrayPath); auto& dataStore = dataArray->template getIDataStoreRefAs>(); - messageHandler(IFilter::Message::Type::Info, fmt::format("Writing Cell Data {}", arrayPath.getTargetName())); + messageHandler.sendInfoMessage(fmt::format("Writing Cell Data {}", arrayPath.getTargetName())); const usize totalElements = dataStore.getSize(); const int numComps = static_cast(dataStore.getNumberOfComponents()); @@ -305,7 +303,7 @@ struct WriteVtkDataFunctor if(std::chrono::duration_cast(now - start).count() > 1000) { auto string = fmt::format("Processing {}: {}% completed", dataArrayRef.getName(), static_cast(100 * static_cast(idx) / static_cast(numTuples))); - messageHandler(IFilter::Message::Type::Info, string); + messageHandler.sendInfoMessage(string); start = now; if(shouldCancel) { diff --git a/src/Plugins/SimplnxCore/vv/ComputeFeatureNeighborsFilter.md b/src/Plugins/SimplnxCore/vv/ComputeFeatureNeighborsFilter.md index 236cb4ba21..81b7a92a74 100644 --- a/src/Plugins/SimplnxCore/vv/ComputeFeatureNeighborsFilter.md +++ b/src/Plugins/SimplnxCore/vv/ComputeFeatureNeighborsFilter.md @@ -41,7 +41,7 @@ 4. **SurfaceFeatures 2D/1D fix** — legacy `FindNeighbors.cpp:330–343` only handles `ZPoints==1` explicitly; the 3D branch fires for any image with `YPoints==1` or `XPoints==1` and always triggers on `row==0` or `column==0` (both always true when that axis has length 1), marking every feature as surface. SIMPLNX uses per-dimensionality specializations that only mark corner cells (1D), corner+edge cells (2D), or all boundary cells (3D) as surface. This is D2. 5. **Two-stage boundary split** — Stage 1 processes corner/edge/face boundary cells with explicit face-validity checks; Stage 2 processes internal cells without validity overhead. Structure is new; behavior-equivalent for 3D inputs. 6. **Template specialization on optional outputs** — `ComputeFeatureNeighborsFunctor` avoids runtime branches in the per-voxel hot path. Performance only; no behavior delta. -7. **`ThrottledMessenger` progress** — legacy used direct `notifyStatusMessage`; SIMPLNX uses `ThrottledMessenger`. UX-only. +7. **Throttled progress** — legacy used direct `notifyStatusMessage`; SIMPLNX emits throttled progress feedback. UX-only. *Material PRs since baseline (2025-10-01):* diff --git a/src/Plugins/SimplnxCore/vv/ComputeGroupingDensityFilter.md b/src/Plugins/SimplnxCore/vv/ComputeGroupingDensityFilter.md index 7203df4136..210126932d 100644 --- a/src/Plugins/SimplnxCore/vv/ComputeGroupingDensityFilter.md +++ b/src/Plugins/SimplnxCore/vv/ComputeGroupingDensityFilter.md @@ -13,7 +13,7 @@ | Aspect | Current state | |------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Algorithm Relationship | **Port** of legacy `FindGroupingDensity::execute()` from `tuks188/DREAM3D` `feature/770_Grouping_Density` (an un-merged feature branch on a contributor fork — never shipped in any DREAM3D 6.5.x release). Same SIMPL UUID retained. Six port-time deltas documented (`QVector→unordered_set` membership swap; runtime `if`→`if constexpr` specializations; conditional `checkedFeatureVolumes` allocation; added `m_ShouldCancel` + `ThrottledMessenger`) — all performance/cancel/progress-only, none change output. **Caveat:** a small number of customers consumed a custom DREAM3D 6.5.x build that included this legacy filter, so the diff-explanation policy applies with a narrower migrant audience than usual. | +| Algorithm Relationship | **Port** of legacy `FindGroupingDensity::execute()` from `tuks188/DREAM3D` `feature/770_Grouping_Density` (an un-merged feature branch on a contributor fork — never shipped in any DREAM3D 6.5.x release). Same SIMPL UUID retained. Six port-time deltas documented (`QVector→unordered_set` membership swap; runtime `if`→`if constexpr` specializations; conditional `checkedFeatureVolumes` allocation; added `m_ShouldCancel` + throttled progress feedback) — all performance/cancel/progress-only, none change output. **Caveat:** a small number of customers consumed a custom DREAM3D 6.5.x build that included this legacy filter, so the diff-explanation policy applies with a narrower migrant audience than usual. | | Oracle (confirmed) | **Class 1 (Analytical) primary** — hand-derivation embedded in the Oracle section (set-union sums + ratio division on the 5-feature v2 hand-built fixture; 4-configuration expected-output table). **Class 4 (Invariant) companion** — sentinel `-1.0f` on empty parents; `density[0] == 0.0f`; `density[i] ≤ 1.0f`; `density[i] > 0 ∨ == -1.0f`; `CheckedFeatures` range — asserted inline. **Second-engineer review skipped** (recorded reason): 5-feature hand-built fixture arithmetic is high-school-level set-union + ratio; bit-identical cross-check against an independently-built legacy serves as external validation. | | Code paths enumerated | **7 of 7 paths** exercised: 4 template specializations of `FindDensityGrouping` (all 4 covered by the DYNAMIC_SECTION A/B test) + empty-parent sentinel (Class 4 edge-case test) + cancellation check (`m_ShouldCancel` in outer parent loop — structurally covered, not exercised by V&V fixtures by design) + preflight error paths (4 dedicated error tests). | | Tests today | **7 TEST_CASEs**, 100% pass. 1 DYNAMIC_SECTION 4-config exemplar A/B (covers all 4 `(UseNonContiguous, FindCheckedFeatures)` template specializations) + 1 Class 4 empty-parent sentinel test + 4 preflight-error tests (one each for -15670/-15671/-15672/-15673) + 1 SIMPL backwards-compat test. **Restructured from 8 → 7 tests**: 5 redundant pre-V&V tests (4 `(NC,CF)` execution variants + 1 v1-exemplar consumer) replaced by the single DYNAMIC_SECTION A/B; added: empty-parent sentinel + preflight error -15672 (closed a coverage gap); kept: 3 existing preflight errors + SIMPL backwards-compat. | @@ -43,7 +43,7 @@ 3. Runtime `for(k=0; k checkedfeaturevolumes(numfeatures, 0.0f)` always allocated → conditionally allocated only when `FindCheckedFeatures==true` — memory savings, no behavior change (legacy zeros were unread when the flag was false). 5. Added: `m_ShouldCancel` check in the outer parent loop (legacy has no cancel support). -6. Added: `ThrottledMessenger` per-parent progress (legacy emits one terminal "Complete" message). +6. Added: throttled per-parent progress feedback (legacy emits one terminal "Complete" message). *Material PRs since baseline (2025-10-01):* diff --git a/src/Plugins/SimplnxCore/vv/deviations/ComputeGroupingDensityFilter.md b/src/Plugins/SimplnxCore/vv/deviations/ComputeGroupingDensityFilter.md index 33283b0fc1..5ec1b90fc9 100644 --- a/src/Plugins/SimplnxCore/vv/deviations/ComputeGroupingDensityFilter.md +++ b/src/Plugins/SimplnxCore/vv/deviations/ComputeGroupingDensityFilter.md @@ -77,7 +77,7 @@ output-preserving: 4. **Conditional allocation:** `QVector checkedfeaturevolumes(numfeatures, 0.0f)` always allocated → conditionally allocated only when `FindCheckedFeatures==true`. **No output change** (legacy zeros were never read when the flag was false). 5. **Cancellation support:** added `m_ShouldCancel` check in the outer parent loop. (Legacy had no cancel support; A/B was run without cancel so this code path is not exercised by the comparison.) -6. **Progress reporting:** added `ThrottledMessenger` per-parent progress. (Legacy emits one terminal "Complete" message; SIMPLNX emits per-parent updates. Affects logs only.) +6. **Progress reporting:** added throttled per-parent progress feedback. (Legacy emits one terminal "Complete" message; SIMPLNX emits per-parent updates. Affects logs only.) Both implementations also behave the same way for the `FindCheckedFeatures=false` cases — **neither** writes the `CheckedFeatures` output array (it's omitted, not diff --git a/src/Plugins/SimplnxCore/wrapping/python/simplnxpy.cpp b/src/Plugins/SimplnxCore/wrapping/python/simplnxpy.cpp index ab82dc4cbd..a85f6814da 100644 --- a/src/Plugins/SimplnxCore/wrapping/python/simplnxpy.cpp +++ b/src/Plugins/SimplnxCore/wrapping/python/simplnxpy.cpp @@ -1482,24 +1482,46 @@ PYBIND11_MODULE(simplnx, mod) filterMessage.def(py::init<>()); filterMessage.def(py::init()); + filterMessage.def(py::init([](IFilter::Message::Type type, std::string message, int32 progress) { + return IFilter::Message{type, std::move(message), progress}; + }), + "type"_a, "message"_a, "progress"_a); filterMessage.def_readwrite("type", &IFilter::Message::type); filterMessage.def_readwrite("message", &IFilter::Message::message); - - py::class_ progressMessage(filter, "ProgressMessage"); - progressMessage.def(py::init([](std::string message, int32 progress) { - IFilter::ProgressMessage progressMessage_; - progressMessage_.type = IFilter::Message::Type::Progress; - progressMessage_.message = std::move(message); - progressMessage_.progress = progress; - return progressMessage_; - }), - "message"_a, "progress"_a); - progressMessage.def_readwrite("progress", &IFilter::ProgressMessage::progress); + filterMessage.def_readwrite("progress", &IFilter::Message::progress); + + // Progress information now lives on Message itself, so ProgressMessage is no longer a distinct + // type. This factory keeps the IFilter.ProgressMessage(message, progress) call shape working. + filter.def_static( + "ProgressMessage", + [](std::string message, int32 progress) { + return IFilter::Message{IFilter::Message::Type::Progress, std::move(message), progress}; + }, + "message"_a, "progress"_a); py::class_ messageHandler(filter, "MessageHandler"); messageHandler.def(py::init<>()); messageHandler.def_readwrite("callback", &IFilter::MessageHandler::m_Callback); - messageHandler.def("__call__", [](const IFilter::MessageHandler& self, const IFilter::Message& message) { self(message); }); + messageHandler.def( + "send_message", [](const IFilter::MessageHandler& self, const IFilter::Message& message) { self.sendMessage(message); }, "message"_a); + messageHandler.def( + "send_info_message", [](const IFilter::MessageHandler& self, std::string message) { self.sendInfoMessage(std::move(message)); }, "message"_a); + messageHandler.def( + "send_debug_message", [](const IFilter::MessageHandler& self, std::string message) { self.sendDebugMessage(std::move(message)); }, "message"_a); + messageHandler.def( + "send_warning_message", [](const IFilter::MessageHandler& self, std::string message) { self.sendWarningMessage(std::move(message)); }, "message"_a); + messageHandler.def( + "send_error_message", [](const IFilter::MessageHandler& self, std::string message) { self.sendErrorMessage(std::move(message)); }, "message"_a); + messageHandler.def( + "send_progress_message", [](const IFilter::MessageHandler& self, std::string message, int32 percent) { self.sendProgressMessage(std::move(message), percent); }, "message"_a, "percent"_a); + messageHandler.def( + "send_progress_count", [](const IFilter::MessageHandler& self, std::string label, usize current, usize max) { self.sendProgressCount(std::move(label), current, max); }, "label"_a, "current"_a, + "max"_a); + messageHandler.def( + "send_progress_percent", + [](const IFilter::MessageHandler& self, std::string label, usize current, usize max, int32 decimals) { self.sendProgressPercent(std::move(label), current, max, decimals); }, "label"_a, + "current"_a, "max"_a, "decimals"_a = 2); + messageHandler.def("__call__", [](const IFilter::MessageHandler& self, const IFilter::Message& message) { self.sendMessage(message); }); py::class_ preflightValue(filter, "PreflightValue"); preflightValue.def(py::init<>()); diff --git a/src/nxrunner/src/CliObserver.cpp b/src/nxrunner/src/CliObserver.cpp index 89f9540f92..3f8ba69938 100644 --- a/src/nxrunner/src/CliObserver.cpp +++ b/src/nxrunner/src/CliObserver.cpp @@ -16,9 +16,6 @@ PipelineObserver::PipelineObserver(Pipeline* pipeline) { startObservingNode(pipeline); pipeline->getCancelledSignal().connect([this](void) { onCancelled(); }); - pipeline->getFilterProgressSignal().connect([this](AbstractPipelineNode* node, int32_t progress, int32_t max, const std::string& msg) { onFilterProgress(node, progress, max, msg); }); - pipeline->getFilterRunStateSignal().connect([this](AbstractPipelineNode* node, int32_t index, RunState state) { onRunStateChanged(node, state); }); - pipeline->getFilterUpdateSignal().connect([this](AbstractPipelineNode* node, int32 index, const std::string& msg) { onFilterUpdate(node, msg); }); pipeline->getPipelineFaultSignal().connect([this](AbstractPipelineNode* node, FaultState state) { onFaultStateChanged(node, state); }); } if(pipeline == nullptr) @@ -83,34 +80,6 @@ void PipelineObserver::onCancelled() const std::cout << timestamp() << " Pipeline has been cancelled" << std::endl; } -void PipelineObserver::onFilterProgress(AbstractPipelineNode* node, int32 progress, int32 maxProgress, const std::string& msg) const -{ - std::cout << fmt::format("{} ({} / {}): {}", node->getName(), progress, maxProgress, msg) << std::endl; -} - -void PipelineObserver::onRunStateChanged(AbstractPipelineNode* node, RunState state) const -{ - switch(state) - { - case RunState::Executing: - std::cout << timestamp() << fmt::format(" {} has begun executing", node->getName()) << std::endl; - break; - case RunState::Preflighting: - std::cout << timestamp() << fmt::format(" {} has begun preflighting", node->getName()) << std::endl; - break; - case RunState::Idle: - std::cout << timestamp() << fmt::format(" {} has completed", node->getName()) << std::endl; - break; - case RunState::Queued: - break; - } -} - -void PipelineObserver::onFilterUpdate(AbstractPipelineNode* node, const std::string& msg) const -{ - std::cout << fmt::format("{}: {}", node->getName(), msg) << std::endl; -} - void PipelineObserver::onFaultStateChanged(AbstractPipelineNode* node, FaultState state) const { switch(state) diff --git a/src/nxrunner/src/CliObserver.hpp b/src/nxrunner/src/CliObserver.hpp index 21e1f57b56..9543e2cca8 100644 --- a/src/nxrunner/src/CliObserver.hpp +++ b/src/nxrunner/src/CliObserver.hpp @@ -27,12 +27,6 @@ class PipelineObserver : public PipelineNodeObserver void onCancelled() const; - void onFilterProgress(AbstractPipelineNode* node, int32 progress, int32 maxProgress, const std::string& msg) const; - - void onRunStateChanged(AbstractPipelineNode* node, RunState state) const; - - void onFilterUpdate(AbstractPipelineNode* node, const std::string& msg) const; - void onFaultStateChanged(AbstractPipelineNode* node, FaultState state) const; private: diff --git a/src/simplnx/Filter/IFilter.cpp b/src/simplnx/Filter/IFilter.cpp index b06536e26b..8aad9212ad 100644 --- a/src/simplnx/Filter/IFilter.cpp +++ b/src/simplnx/Filter/IFilter.cpp @@ -7,11 +7,48 @@ #include #include +#include #include #include using namespace nx::core; +namespace +{ +/** + * @brief Computes the integer percent used to drive a progress bar, clamped to [0, 100]. A zero + * denominator yields 0 rather than dividing by zero. + * @param current + * @param max + * @return + */ +int32 CalculateProgressBarValue(usize current, usize max) +{ + if(max == 0) + { + return 0; + } + auto percent = static_cast(static_cast(current) / static_cast(max) * 100.0); + return std::clamp(percent, 0, 100); +} +} // namespace + +void IFilter::MessageHandler::sendProgressMessage(std::string message, int32 percent) const +{ + sendMessage(Message{Message::Type::Progress, std::move(message), std::clamp(percent, 0, 100)}); +} + +void IFilter::MessageHandler::sendProgressCount(std::string label, usize current, usize max) const +{ + sendMessage(Message{Message::Type::Progress, fmt::format("{}: {}/{}", label, current, max), CalculateProgressBarValue(current, max)}); +} + +void IFilter::MessageHandler::sendProgressPercent(std::string label, usize current, usize max, int32 decimals) const +{ + const float64 percent = (max == 0) ? 0.0 : (static_cast(current) / static_cast(max) * 100.0); + sendMessage(Message{Message::Type::Progress, fmt::format("{}: {:.{}f}%", label, percent, decimals), CalculateProgressBarValue(current, max)}); +} + namespace { template diff --git a/src/simplnx/Filter/IFilter.hpp b/src/simplnx/Filter/IFilter.hpp index 23e1745036..6388d9d788 100644 --- a/src/simplnx/Filter/IFilter.hpp +++ b/src/simplnx/Filter/IFilter.hpp @@ -50,28 +50,38 @@ class SIMPLNX_EXPORT IFilter Type type = Type::Info; std::string message; + + /** + * @brief Percent complete, clamped to [0, 100], for driving a progress bar. A value of -1 means + * this message carries no progress value. The human-readable form of the progress lives in + * `message`, which is already rendered by the sender; this field exists only for consumers that + * need a number. + */ + int32 progress = -1; }; /** - * @brief Extends Message to include progress information. + * @brief Retained as an alias so existing aggregate initializations keep compiling. Progress + * information now lives on Message itself. */ - struct ProgressMessage : public Message - { - int32 progress = 0; - }; + using ProgressMessage = Message; /** * @brief Handler for processing filter messages during execution. + * + * Exported in its own right because MSVC does not propagate __declspec(dllexport) from an + * enclosing class to a nested one. Without this the three out-of-line progress methods are + * missing from simplnx.dll and every plugin that calls one fails to link. */ - struct MessageHandler + struct SIMPLNX_EXPORT MessageHandler { using Callback = std::function; /** - * @brief Invokes the callback with a message. + * @brief Sends a message. * @param message The message to send */ - void operator()(const Message& message) const + void sendMessage(const Message& message) const { if(m_Callback) { @@ -80,34 +90,80 @@ class SIMPLNX_EXPORT IFilter } /** - * @brief Invokes the callback with an info message. + * @brief Sends a message of the given type. + * @param type The message type * @param message The message text */ - void operator()(std::string message) const + void sendMessage(Message::Type type, std::string message) const { - operator()(Message{Message::Type::Info, std::move(message)}); + sendMessage(Message{type, std::move(message)}); } /** - * @brief Invokes the callback with a typed message. - * @param type The message type + * @brief Sends an informational message. * @param message The message text */ - void operator()(Message::Type type, std::string message) const + void sendInfoMessage(std::string message) const { - operator()(Message{type, std::move(message)}); + sendMessage(Message{Message::Type::Info, std::move(message)}); } /** - * @brief Invokes the callback with a progress message. - * @param type The message type + * @brief Sends a debug message. + * @param message The message text + */ + void sendDebugMessage(std::string message) const + { + sendMessage(Message{Message::Type::Debug, std::move(message)}); + } + + /** + * @brief Sends a warning message. + * @param message The message text + */ + void sendWarningMessage(std::string message) const + { + sendMessage(Message{Message::Type::Warning, std::move(message)}); + } + + /** + * @brief Sends an error message. * @param message The message text - * @param progress The progress value */ - void operator()(Message::Type type, std::string message, int32 progress) const + void sendErrorMessage(std::string message) const { - operator()(ProgressMessage{type, std::move(message), progress}); + sendMessage(Message{Message::Type::Error, std::move(message)}); } + + /** + * @brief Sends a progress message whose text has already been rendered by the caller. Prefer + * sendProgressCount() or sendProgressPercent(), which record what the sender intended to + * display; reach for this only when neither form fits. + * @param message The fully rendered message text + * @param percent Percent complete for the progress bar, clamped to [0, 100] + */ + void sendProgressMessage(std::string message, int32 percent) const; + + /** + * @brief Sends progress as a count of completed items, rendered as "