From 3f558c09ad5d7847758954975ab40bc5243d58fc Mon Sep 17 00:00:00 2001 From: Alexander Vieth Date: Mon, 17 Aug 2026 14:37:07 +0200 Subject: [PATCH 1/9] Remove restrictive condition --- src/ScatterplotPlugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ScatterplotPlugin.cpp b/src/ScatterplotPlugin.cpp index 2f18488..4613951 100644 --- a/src/ScatterplotPlugin.cpp +++ b/src/ScatterplotPlugin.cpp @@ -927,7 +927,7 @@ void ScatterplotPlugin::loadColors(const Dataset& clusters) } } - else if(globalIndices.size() == _numPoints) + else { // Loop over all clusters and populate global colors for (const auto& cluster : clusterVec) From 785d0651f8710a8b68e0262df749be186e66fc89 Mon Sep 17 00:00:00 2001 From: Alexander Vieth Date: Mon, 17 Aug 2026 14:37:29 +0200 Subject: [PATCH 2/9] Use range for, eliminates index --- src/ScatterplotPlugin.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/ScatterplotPlugin.cpp b/src/ScatterplotPlugin.cpp index 4613951..d3a9e3a 100644 --- a/src/ScatterplotPlugin.cpp +++ b/src/ScatterplotPlugin.cpp @@ -918,11 +918,10 @@ void ScatterplotPlugin::loadColors(const Dataset& clusters) if (totalNumPoints == _numPoints && clusterVec.size() == totalNumPoints) { - for (size_t i = 0; i < static_cast(clusterVec.size()); i++) + // Each cluster corresponds to one point + for (const auto& cluster : clusterVec) { - const auto& cluster = clusterVec[i]; const auto color = cluster.getColor(); - localColors[cluster.getIndices()[0]] = Vector3f(color.redF(), color.greenF(), color.blueF()); } From c4100c7404000fa9ed8e9e771516dd16cadeee58 Mon Sep 17 00:00:00 2001 From: Alexander Vieth Date: Mon, 17 Aug 2026 14:37:59 +0200 Subject: [PATCH 3/9] Track totalPoints class wide --- src/ScatterplotPlugin.cpp | 16 +++++++--------- src/ScatterplotPlugin.h | 1 + 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/ScatterplotPlugin.cpp b/src/ScatterplotPlugin.cpp index d3a9e3a..bc52e18 100644 --- a/src/ScatterplotPlugin.cpp +++ b/src/ScatterplotPlugin.cpp @@ -51,6 +51,7 @@ ScatterplotPlugin::ScatterplotPlugin(const PluginFactory* factory) : _dropWidget(nullptr), _scatterPlotWidget(new ScatterplotWidget(this)), _numPoints(0), + _numTotalPoints(0), _settingsAction(new SettingsAction(this, "Settings")), _primaryToolbarAction(new HorizontalToolbarAction(this, "Primary Toolbar")) { @@ -707,6 +708,10 @@ void ScatterplotPlugin::positionDatasetChanged() _numPoints = _positionDataset->getNumPoints(); + _numTotalPoints = _positionDataset->isDerivedData() + ? _positionSourceDataset->getFullDataset()->getNumPoints() + : _positionDataset->getFullDataset()->getNumPoints(); + _scatterPlotWidget->getPointRendererNavigator().resetView(true); _scatterPlotWidget->getDensityRendererNavigator().resetView(true); @@ -899,24 +904,17 @@ void ScatterplotPlugin::loadColors(const Dataset& clusters) if (!clusters.isValid() || !_positionDataset.isValid()) return; - // Get global indices from the position dataset - std::uint64_t totalNumPoints = 0; - if (_positionDataset->isDerivedData()) - totalNumPoints = _positionSourceDataset->getFullDataset()->getNumPoints(); - else - totalNumPoints = _positionDataset->getFullDataset()->getNumPoints(); - // Mapping from local to global indices std::vector globalIndices; _positionDataset->getGlobalIndices(globalIndices); // Generate color buffer for global and local colors - std::vector globalColors(totalNumPoints); + std::vector globalColors(_numTotalPoints); std::vector localColors(_numPoints); const auto& clusterVec = clusters->getClusters(); - if (totalNumPoints == _numPoints && clusterVec.size() == totalNumPoints) + if (_numTotalPoints == _numPoints && static_cast(clusterVec.size()) == _numTotalPoints) { // Each cluster corresponds to one point for (const auto& cluster : clusterVec) diff --git a/src/ScatterplotPlugin.h b/src/ScatterplotPlugin.h index ef32af8..c8e2b42 100644 --- a/src/ScatterplotPlugin.h +++ b/src/ScatterplotPlugin.h @@ -148,6 +148,7 @@ class ScatterplotPlugin : public ViewPlugin Dataset _positionSourceDataset; /** Smart pointer to source of the points dataset for point position (if any) */ std::vector _positions; /** Point positions */ std::uint64_t _numPoints; /** Number of point positions */ + std::uint64_t _numTotalPoints; /** Number of points in positions data set (might be more than _numPoints) */ QPointer _settingsAction; /** Group action for all settings */ QPointer _primaryToolbarAction; /** Horizontal toolbar for primary content */ QRectF _selectionBoundaries; /** Boundaries of the selection */ From d12469272081d9cd2c8a67559260c666bff871c3 Mon Sep 17 00:00:00 2001 From: Alexander Vieth Date: Mon, 17 Aug 2026 14:38:29 +0200 Subject: [PATCH 4/9] Check if cluster indices exceed point indices instead of checking of they provide full coverage --- src/ScatterplotPlugin.cpp | 64 +++++++++++++++++++++++++++++++-------- 1 file changed, 51 insertions(+), 13 deletions(-) diff --git a/src/ScatterplotPlugin.cpp b/src/ScatterplotPlugin.cpp index bc52e18..6bde5ea 100644 --- a/src/ScatterplotPlugin.cpp +++ b/src/ScatterplotPlugin.cpp @@ -34,10 +34,28 @@ #include #include #include +#include #include #include #include +#ifdef __cpp_lib_execution +#ifdef __GNUC__ // both TBB and Qt define emit keyword: undef +#undef emit +#endif +#include +#ifdef __GNUC__ // both TBB and Qt define emit keyword: def again +#define emit +#endif +#ifdef NDEBUG +#define MV_SCATTER_PARALLEL_EXECUTION std::execution::par, +#else +#define MV_SCATTER_PARALLEL_EXECUTION std::execution::seq, +#endif +#else +#define MV_SCATTER_PARALLEL_EXECUTION +#endif + #define VIEW_SAMPLING_HTML //#define VIEW_SAMPLING_WIDGET @@ -246,19 +264,39 @@ ScatterplotPlugin::ScatterplotPlugin(const PluginFactory* factory) : { // Check to set whether the number of data points comprised throughout all clusters is the same number // as the number of data points in the dataset we are trying to color - std::uint64_t totalNumIndices = 0; - for (const Cluster& cluster : candidateDataset->getClusters()) - { - totalNumIndices += cluster.getIndices().size(); - } - - std::uint64_t totalNumPoints = 0; - if (_positionDataset->isDerivedData()) - totalNumPoints = _positionSourceDataset->getFullDataset()->getNumPoints(); - else - totalNumPoints = _positionDataset->getFullDataset()->getNumPoints(); - - if (totalNumIndices == totalNumPoints) + //std::uint64_t totalNumIndices = 0; + //for (const Cluster& cluster : candidateDataset->getClusters()) + //{ + // totalNumIndices += cluster.getIndices().size(); + //} + + auto getMaxIndex = [](const QVector& clusters) -> std::uint32_t + { + if (clusters.empty()) + return std::numeric_limits::lowest(); + + std::vector clusterIndicesMax(clusters.size()); + + std::transform( + MV_SCATTER_PARALLEL_EXECUTION + clusters.cbegin(), clusters.cend(), + clusterIndicesMax.begin(), + [](const Cluster& cluster) -> std::uint32_t { + const std::vector& indices = cluster.getIndices(); + if (indices.empty()) + return std::numeric_limits::lowest(); + + return *std::ranges::max_element(indices); + }); + + return *std::max_element( + MV_SCATTER_PARALLEL_EXECUTION + clusterIndicesMax.cbegin(), clusterIndicesMax.cend()); + }; + + const auto maxIndex = getMaxIndex(candidateDataset->getClusters()); + + if (maxIndex < _numTotalPoints) { // Use the clusters set for points color dropRegions << new DropWidget::DropRegion(this, "Color", description, "palette", true, [this, candidateDataset]() { From aeaab89238f3c0617a0877084cabed4ce2f77549 Mon Sep 17 00:00:00 2001 From: Alexander Vieth Date: Mon, 17 Aug 2026 14:50:06 +0200 Subject: [PATCH 5/9] Update comment --- src/ScatterplotPlugin.cpp | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/ScatterplotPlugin.cpp b/src/ScatterplotPlugin.cpp index 6bde5ea..99ab906 100644 --- a/src/ScatterplotPlugin.cpp +++ b/src/ScatterplotPlugin.cpp @@ -262,14 +262,7 @@ ScatterplotPlugin::ScatterplotPlugin(const PluginFactory* factory) : else { if (candidateDataset.isValid()) { - // Check to set whether the number of data points comprised throughout all clusters is the same number - // as the number of data points in the dataset we are trying to color - //std::uint64_t totalNumIndices = 0; - //for (const Cluster& cluster : candidateDataset->getClusters()) - //{ - // totalNumIndices += cluster.getIndices().size(); - //} - + // Check that the max index in the cluster data does not exceed the max index of the shown point data auto getMaxIndex = [](const QVector& clusters) -> std::uint32_t { if (clusters.empty()) From 933bcfe34cd73b5c39017c0f8ef5f68c2f9a1753 Mon Sep 17 00:00:00 2001 From: Alexander Vieth Date: Tue, 18 Aug 2026 11:01:26 +0200 Subject: [PATCH 6/9] Getter instead of member var --- src/ScatterplotPlugin.cpp | 24 ++++++++++++++++-------- src/ScatterplotPlugin.h | 7 ++++++- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/ScatterplotPlugin.cpp b/src/ScatterplotPlugin.cpp index 99ab906..4f876fd 100644 --- a/src/ScatterplotPlugin.cpp +++ b/src/ScatterplotPlugin.cpp @@ -69,7 +69,6 @@ ScatterplotPlugin::ScatterplotPlugin(const PluginFactory* factory) : _dropWidget(nullptr), _scatterPlotWidget(new ScatterplotWidget(this)), _numPoints(0), - _numTotalPoints(0), _settingsAction(new SettingsAction(this, "Settings")), _primaryToolbarAction(new HorizontalToolbarAction(this, "Primary Toolbar")) { @@ -289,7 +288,7 @@ ScatterplotPlugin::ScatterplotPlugin(const PluginFactory* factory) : const auto maxIndex = getMaxIndex(candidateDataset->getClusters()); - if (maxIndex < _numTotalPoints) + if (maxIndex < numTotalPoints()) { // Use the clusters set for points color dropRegions << new DropWidget::DropRegion(this, "Color", description, "palette", true, [this, candidateDataset]() { @@ -739,16 +738,22 @@ void ScatterplotPlugin::positionDatasetChanged() _numPoints = _positionDataset->getNumPoints(); - _numTotalPoints = _positionDataset->isDerivedData() - ? _positionSourceDataset->getFullDataset()->getNumPoints() - : _positionDataset->getFullDataset()->getNumPoints(); - _scatterPlotWidget->getPointRendererNavigator().resetView(true); _scatterPlotWidget->getDensityRendererNavigator().resetView(true); updateData(); } +std::uint64_t ScatterplotPlugin::numTotalPoints() const +{ + if (!_positionDataset.isValid()) + return 0; + + return _positionDataset->isDerivedData() + ? _positionSourceDataset->getFullDataset()->getNumPoints() + : _positionDataset->getFullDataset()->getNumPoints(); +} + bool ScatterplotPlugin::mapColorScalars(const Dataset& pointsColor, const std::uint32_t& dimensionIndex, std::vector& colorScalars) { // Only proceed with valid points dataset @@ -935,17 +940,20 @@ void ScatterplotPlugin::loadColors(const Dataset& clusters) if (!clusters.isValid() || !_positionDataset.isValid()) return; + // Get global indices from the position dataset + const std::uint64_t totalNumPoints = numTotalPoints(); + // Mapping from local to global indices std::vector globalIndices; _positionDataset->getGlobalIndices(globalIndices); // Generate color buffer for global and local colors - std::vector globalColors(_numTotalPoints); + std::vector globalColors(totalNumPoints); std::vector localColors(_numPoints); const auto& clusterVec = clusters->getClusters(); - if (_numTotalPoints == _numPoints && static_cast(clusterVec.size()) == _numTotalPoints) + if (totalNumPoints == _numPoints && static_cast(clusterVec.size()) == totalNumPoints) { // Each cluster corresponds to one point for (const auto& cluster : clusterVec) diff --git a/src/ScatterplotPlugin.h b/src/ScatterplotPlugin.h index c8e2b42..c04e6a8 100644 --- a/src/ScatterplotPlugin.h +++ b/src/ScatterplotPlugin.h @@ -141,6 +141,12 @@ class ScatterplotPlugin : public ViewPlugin */ bool mapColorScalars(const Dataset& pointsColor, const std::uint32_t& dimensionIndex, std::vector& colorScalars); + /** + * Number of points in positions data set (might be more than _numPoints) + * @return Number of points in positions data set (might be more than _numPoints) + */ + std::uint64_t numTotalPoints() const; + private: mv::gui::DropWidget* _dropWidget; /** Widget for dropping datasets */ ScatterplotWidget* _scatterPlotWidget; /** The visualization widget */ @@ -148,7 +154,6 @@ class ScatterplotPlugin : public ViewPlugin Dataset _positionSourceDataset; /** Smart pointer to source of the points dataset for point position (if any) */ std::vector _positions; /** Point positions */ std::uint64_t _numPoints; /** Number of point positions */ - std::uint64_t _numTotalPoints; /** Number of points in positions data set (might be more than _numPoints) */ QPointer _settingsAction; /** Group action for all settings */ QPointer _primaryToolbarAction; /** Horizontal toolbar for primary content */ QRectF _selectionBoundaries; /** Boundaries of the selection */ From 998e4495a49a2cd9b71ac426e21bde67d1e7dd42 Mon Sep 17 00:00:00 2001 From: Alexander Vieth Date: Tue, 18 Aug 2026 11:01:36 +0200 Subject: [PATCH 7/9] No need to double set _numPoints --- src/ScatterplotPlugin.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/ScatterplotPlugin.cpp b/src/ScatterplotPlugin.cpp index 4f876fd..092ebcb 100644 --- a/src/ScatterplotPlugin.cpp +++ b/src/ScatterplotPlugin.cpp @@ -729,15 +729,8 @@ void ScatterplotPlugin::positionDatasetChanged() if (!_positionDataset.isValid()) return; - // Reset dataset references - //_positionSourceDataset.reset(); - - // Set position source dataset reference when the position dataset is derived - //if (_positionDataset->isDerivedData()) _positionSourceDataset = _positionDataset->getSourceDataset(); - _numPoints = _positionDataset->getNumPoints(); - _scatterPlotWidget->getPointRendererNavigator().resetView(true); _scatterPlotWidget->getDensityRendererNavigator().resetView(true); From 576ee4a4857be4fc64778eb5f4feaeb571af9f01 Mon Sep 17 00:00:00 2001 From: Alexander Vieth Date: Tue, 18 Aug 2026 11:07:42 +0200 Subject: [PATCH 8/9] Let's not do this unrelated change in this PR --- src/ScatterplotPlugin.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ScatterplotPlugin.cpp b/src/ScatterplotPlugin.cpp index 092ebcb..41383a4 100644 --- a/src/ScatterplotPlugin.cpp +++ b/src/ScatterplotPlugin.cpp @@ -730,6 +730,8 @@ void ScatterplotPlugin::positionDatasetChanged() return; _positionSourceDataset = _positionDataset->getSourceDataset(); + + _numPoints = _positionDataset->getNumPoints(); _scatterPlotWidget->getPointRendererNavigator().resetView(true); _scatterPlotWidget->getDensityRendererNavigator().resetView(true); From fa4d860b30a020980cf3a6b8f0e47513156539b6 Mon Sep 17 00:00:00 2001 From: Alexander Vieth Date: Tue, 18 Aug 2026 11:28:58 +0200 Subject: [PATCH 9/9] link against tbb with gcc --- CMakeLists.txt | 8 ++++++++ conanfile.py | 5 +++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 39453a5..f3fc5aa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,6 +25,10 @@ find_package(Qt6 COMPONENTS Widgets WebEngineWidgets OpenGL OpenGLWidgets REQUIR find_package(ManiVault COMPONENTS Core PointData ClusterData ColorData ImageData CONFIG QUIET) mv_project_defaults() +if(UNIX AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + find_package(TBB REQUIRED) +endif() + # ----------------------------------------------------------------------------- # Source files # ----------------------------------------------------------------------------- @@ -126,6 +130,10 @@ target_link_libraries(${PROJECT} PRIVATE ManiVault::ClusterData) target_link_libraries(${PROJECT} PRIVATE ManiVault::ImageData) target_link_libraries(${PROJECT} PRIVATE ManiVault::ColorData) +if(UNIX AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + target_link_libraries(${PROJECT} PRIVATE TBB::tbb) +endif() + # ----------------------------------------------------------------------------- # Target installation # ----------------------------------------------------------------------------- diff --git a/conanfile.py b/conanfile.py index a8ba72b..1980dd0 100644 --- a/conanfile.py +++ b/conanfile.py @@ -1,6 +1,7 @@ from conans import ConanFile from conan.tools.cmake import CMakeDeps, CMake, CMakeToolchain from conans.tools import save, load +from conans.tools import os_info from conans import tools import os import pathlib @@ -72,8 +73,8 @@ def configure(self): pass def system_requirements(self): - # May be needed for macOS or Linux - pass + if os_info.is_linux: + self.run("sudo apt update && sudo apt install -y libtbb-dev") def config_options(self): if self.settings.os == "Windows":