From 6f8bf2b99ea8897b4dda39cf6dffd685f27e3082 Mon Sep 17 00:00:00 2001 From: Alexander Vieth Date: Tue, 19 Aug 2025 16:10:30 +0200 Subject: [PATCH 01/16] WIP: color by selection mapping --- src/ScatterplotPlugin.cpp | 101 +++++++++++++++++++++++++++++++++++--- 1 file changed, 95 insertions(+), 6 deletions(-) diff --git a/src/ScatterplotPlugin.cpp b/src/ScatterplotPlugin.cpp index 6a3136e..a4ddacf 100644 --- a/src/ScatterplotPlugin.cpp +++ b/src/ScatterplotPlugin.cpp @@ -31,6 +31,9 @@ #include #include +#include +#include +#include #include #define VIEW_SAMPLING_HTML @@ -41,6 +44,75 @@ Q_PLUGIN_METADATA(IID "studio.manivault.ScatterplotPlugin") using namespace mv; using namespace mv::util; +static std::optional getSelectionMapping(const mv::Dataset& source, const mv::Dataset& target) { + const std::vector& linkedDatas = source->getLinkedData(); + + qDebug() << "Source: " << source->getGuiName(); + + for (const auto& linkedData : linkedDatas) { + qDebug() << linkedData.getSourceDataSet()->getGuiName(); + qDebug() << linkedData.getTargetDataset()->getGuiName(); + } + + const auto it = std::ranges::find_if(linkedDatas, [&target](const mv::LinkedData& obj) { + + // TODO: This should be recursive + auto isParentOf = [&target](const mv::Dataset& linkedTarget) -> bool { + if (target->isDerivedData()) { + const auto parent = target->getParent(); + if (parent->getDataType() == PointType) { + const auto parentPoints = mv::Dataset(parent); + + qDebug() << parentPoints->getGuiName(); + qDebug() << target->getGuiName(); + + return parentPoints->getNumPoints() == target->getNumPoints(); + } + } + return false; + }; + + return obj.getTargetDataset() == target || isParentOf(obj.getTargetDataset()); + }); + + if (it != linkedDatas.end()) { + return &(*it); // return pointer to the found object + } + + return std::nullopt; // nothing found +} + +static bool checkSelectionMapping(const mv::Dataset& source, const mv::Dataset& target) { + const std::vector& linkedDatas = source->getLinkedData(); + + // First, check if there is a mapping + const auto it = getSelectionMapping(source, target); + + if (!it.has_value()) + return false; + + // Second, check if the mapping is surjective, i.e. hits all elements in the target + const std::map>& linkedMap = it.value()->getMapping().getMap(); + const std::uint32_t numPointsInTarget = target->getNumPoints(); + + std::vector found(numPointsInTarget, false); + std::uint32_t count = 0; + + for (const auto& [key, vec] : linkedMap) { + for (std::uint32_t val : vec) { + if (val >= numPointsInTarget) continue; // Skip values that are too large + + if (!found[val]) { + found[val] = true; + if (++count == numPointsInTarget) + return true; + } + } + } + + return false; // The previous loop would have returned early if the entire taget set was covered +} + ScatterplotPlugin::ScatterplotPlugin(const PluginFactory* factory) : ViewPlugin(factory), _dropWidget(nullptr), @@ -181,8 +253,9 @@ ScatterplotPlugin::ScatterplotPlugin(const PluginFactory* factory) : /*if*/ _positionDataset->isDerivedData() ? /*then*/ _positionDataset->getSourceDataset()->getFullDataset()->getNumPoints() == numPointsCandidate : /*else*/ false; + const bool hasSelectionMapping = checkSelectionMapping(candidateDataset, _positionDataset); - if (sameNumPoints || sameNumPointsAsFull) { + if (sameNumPoints || sameNumPointsAsFull || hasSelectionMapping) { // Offer the option to use the points dataset as source for points colors dropRegions << new DropWidget::DropRegion(this, "Point color", QString("Colorize %1 points with %2").arg(_positionDataset->text(), candidateDataset->text()), "palette", true, [this, candidateDataset]() { _settingsAction.getColoringAction().setCurrentColorDataset(candidateDataset); // calls addColorDataset internally @@ -647,19 +720,18 @@ void ScatterplotPlugin::positionDatasetChanged() updateData(); } -void ScatterplotPlugin::loadColors(const Dataset& points, const std::uint32_t& dimensionIndex) +void ScatterplotPlugin::loadColors(const Dataset& pointsColor, const std::uint32_t& dimensionIndex) { // Only proceed with valid points dataset - if (!points.isValid()) + if (!pointsColor.isValid()) return; // Generate point scalars for color mapping std::vector scalars; - points->extractDataForDimension(scalars, dimensionIndex); - - const auto numColorPoints = points->getNumPoints(); + pointsColor->extractDataForDimension(scalars, dimensionIndex); + const auto numColorPoints = pointsColor->getNumPoints(); if (numColorPoints != _numPoints) { @@ -668,6 +740,8 @@ void ScatterplotPlugin::loadColors(const Dataset& points, const std::uin /*then*/ _positionSourceDataset->getFullDataset()->getNumPoints() == numColorPoints : /*else*/ false; + const auto validSelectionMapping = getSelectionMapping(pointsColor, _positionDataset); + if (sameNumPointsAsFull) { std::vector globalIndices; _positionDataset->getGlobalIndices(globalIndices); @@ -680,6 +754,21 @@ void ScatterplotPlugin::loadColors(const Dataset& points, const std::uin std::swap(localScalars, scalars); } + else if (validSelectionMapping.has_value() && validSelectionMapping.value() != nullptr) { + std::vector localScalars(_numPoints, 0); + + // Map values like selection + const mv::SelectionMap::Map& linkedMap = validSelectionMapping.value()->getMapping().getMap(); + const std::uint32_t numPointsInTarget = _positionDataset->getNumPoints(); + + for (const auto& [fromID, vecOfIDs] : linkedMap) { + for (std::uint32_t toID : vecOfIDs) { + localScalars[toID] = scalars[fromID]; + } + } + + std::swap(localScalars, scalars); + } else { qWarning("Number of points used for coloring does not match number of points in data, aborting attempt to color plot"); return; From eb1ab5a377f53ca8bcb9b86ba4c8bb5df515bfc6 Mon Sep 17 00:00:00 2001 From: Alexander Vieth Date: Thu, 21 Aug 2025 11:25:57 +0200 Subject: [PATCH 02/16] Add explanations --- src/ScatterplotPlugin.cpp | 53 +++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/src/ScatterplotPlugin.cpp b/src/ScatterplotPlugin.cpp index a4ddacf..24a4d0b 100644 --- a/src/ScatterplotPlugin.cpp +++ b/src/ScatterplotPlugin.cpp @@ -44,28 +44,20 @@ Q_PLUGIN_METADATA(IID "studio.manivault.ScatterplotPlugin") using namespace mv; using namespace mv::util; +// returns a selection map between source and target static std::optional getSelectionMapping(const mv::Dataset& source, const mv::Dataset& target) { const std::vector& linkedDatas = source->getLinkedData(); - qDebug() << "Source: " << source->getGuiName(); - - for (const auto& linkedData : linkedDatas) { - qDebug() << linkedData.getSourceDataSet()->getGuiName(); - qDebug() << linkedData.getTargetDataset()->getGuiName(); - } - + // find linked data between source and target OR source and target's parent, if target is derived and they have the same number of points const auto it = std::ranges::find_if(linkedDatas, [&target](const mv::LinkedData& obj) { - // TODO: This should be recursive + // This only checks the immedeate parent and is deliberately not recursive + // We might consider the latter in the future, but would need to cover more edge cases auto isParentOf = [&target](const mv::Dataset& linkedTarget) -> bool { if (target->isDerivedData()) { const auto parent = target->getParent(); if (parent->getDataType() == PointType) { const auto parentPoints = mv::Dataset(parent); - - qDebug() << parentPoints->getGuiName(); - qDebug() << target->getGuiName(); - return parentPoints->getNumPoints() == target->getNumPoints(); } } @@ -82,16 +74,16 @@ static std::optional getSelectionMapping(const mv::Datase return std::nullopt; // nothing found } +// returns whether there is a selection map from source to target that covers all elements in the target static bool checkSelectionMapping(const mv::Dataset& source, const mv::Dataset& target) { - const std::vector& linkedDatas = source->getLinkedData(); - // First, check if there is a mapping + // Check if there is a mapping const auto it = getSelectionMapping(source, target); - if (!it.has_value()) + if (!it.has_value() || it.value() == nullptr) return false; - // Second, check if the mapping is surjective, i.e. hits all elements in the target + // Check if the mapping is surjective, i.e. hits all elements in the target const std::map>& linkedMap = it.value()->getMapping().getMap(); const std::uint32_t numPointsInTarget = target->getNumPoints(); @@ -244,18 +236,26 @@ ScatterplotPlugin::ScatterplotPlugin(const PluginFactory* factory) : }); } - // Accept both data with the same number if points and data which is derived from - // a parent that has the same number of points (e.g. for HSNE embeddings) + // Accept for recoloring: + // 1. data with the same number of points + // 2. data which is derived from a parent that has the same number of points (e.g. for HSNE embeddings), where we can use global indices for mapping + // 3. data which has a fully-covering selection mapping, that we can use for setting colors + + // [1. Same number of points] const auto numPointsCandidate = candidateDataset->getNumPoints(); const auto numPointsPosition = _positionDataset->getNumPoints(); - const bool sameNumPoints = numPointsPosition == numPointsCandidate; - const bool sameNumPointsAsFull = + const bool hasSameNumPoints = numPointsPosition == numPointsCandidate; + + // [2. Derived from a parent] + const bool hasSameNumPointsAsFull = /*if*/ _positionDataset->isDerivedData() ? /*then*/ _positionDataset->getSourceDataset()->getFullDataset()->getNumPoints() == numPointsCandidate : /*else*/ false; - const bool hasSelectionMapping = checkSelectionMapping(candidateDataset, _positionDataset); - if (sameNumPoints || sameNumPointsAsFull || hasSelectionMapping) { + // [3. Full selection mapping] + const bool hasSelectionMapping = checkSelectionMapping(candidateDataset, _positionDataset); + + if (hasSameNumPoints || hasSameNumPointsAsFull || hasSelectionMapping) { // Offer the option to use the points dataset as source for points colors dropRegions << new DropWidget::DropRegion(this, "Point color", QString("Colorize %1 points with %2").arg(_positionDataset->text(), candidateDataset->text()), "palette", true, [this, candidateDataset]() { _settingsAction.getColoringAction().setCurrentColorDataset(candidateDataset); // calls addColorDataset internally @@ -263,7 +263,9 @@ ScatterplotPlugin::ScatterplotPlugin(const PluginFactory* factory) : } - if (sameNumPoints) { + // Accept for resizing and opacity: Only data with the same number of points + + if (hasSameNumPoints) { // Offer the option to use the points dataset as source for points size dropRegions << new DropWidget::DropRegion(this, "Point size", QString("Size %1 points with %2").arg(_positionDataset->text(), candidateDataset->text()), "ruler-horizontal", true, [this, candidateDataset]() { _settingsAction.getPlotAction().getPointPlotAction().setCurrentPointSizeDataset(candidateDataset); @@ -733,16 +735,17 @@ void ScatterplotPlugin::loadColors(const Dataset& pointsColor, const std const auto numColorPoints = pointsColor->getNumPoints(); + // If number of points do not match, prefer checking for derived data over selection mapping if (numColorPoints != _numPoints) { - const bool sameNumPointsAsFull = + const bool hasSameNumPointsAsFull = /*if*/ _positionDataset->isDerivedData() ? /*then*/ _positionSourceDataset->getFullDataset()->getNumPoints() == numColorPoints : /*else*/ false; const auto validSelectionMapping = getSelectionMapping(pointsColor, _positionDataset); - if (sameNumPointsAsFull) { + if (hasSameNumPointsAsFull) { std::vector globalIndices; _positionDataset->getGlobalIndices(globalIndices); From 6b5681adc1a62a2e812ba53e742a4ae706da66a3 Mon Sep 17 00:00:00 2001 From: Alexander Vieth Date: Thu, 21 Aug 2025 11:26:05 +0200 Subject: [PATCH 03/16] Delay check --- src/ScatterplotPlugin.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/ScatterplotPlugin.cpp b/src/ScatterplotPlugin.cpp index 24a4d0b..24e1479 100644 --- a/src/ScatterplotPlugin.cpp +++ b/src/ScatterplotPlugin.cpp @@ -743,8 +743,6 @@ void ScatterplotPlugin::loadColors(const Dataset& pointsColor, const std /*then*/ _positionSourceDataset->getFullDataset()->getNumPoints() == numColorPoints : /*else*/ false; - const auto validSelectionMapping = getSelectionMapping(pointsColor, _positionDataset); - if (hasSameNumPointsAsFull) { std::vector globalIndices; _positionDataset->getGlobalIndices(globalIndices); @@ -756,12 +754,16 @@ void ScatterplotPlugin::loadColors(const Dataset& pointsColor, const std localScalars[localColorIndex++] = scalars[globalIndex]; std::swap(localScalars, scalars); - } - else if (validSelectionMapping.has_value() && validSelectionMapping.value() != nullptr) { + } + else if ( // only get map if derived check failed + const auto selectionMapping = getSelectionMapping(pointsColor, _positionDataset); + /* check if valid */ selectionMapping.has_value() && selectionMapping.value() != nullptr + ) + { std::vector localScalars(_numPoints, 0); // Map values like selection - const mv::SelectionMap::Map& linkedMap = validSelectionMapping.value()->getMapping().getMap(); + const mv::SelectionMap::Map& linkedMap = selectionMapping.value()->getMapping().getMap(); const std::uint32_t numPointsInTarget = _positionDataset->getNumPoints(); for (const auto& [fromID, vecOfIDs] : linkedMap) { From ecd5b2e6a6dba318c9ce381bac09a86ce33fa799 Mon Sep 17 00:00:00 2001 From: Alexander Vieth Date: Thu, 21 Aug 2025 11:41:23 +0200 Subject: [PATCH 04/16] Better be safe --- src/ScatterplotPlugin.cpp | 69 +++++++++++++++++++++++---------------- 1 file changed, 41 insertions(+), 28 deletions(-) diff --git a/src/ScatterplotPlugin.cpp b/src/ScatterplotPlugin.cpp index 24e1479..1439e67 100644 --- a/src/ScatterplotPlugin.cpp +++ b/src/ScatterplotPlugin.cpp @@ -31,6 +31,7 @@ #include #include +#include #include #include #include @@ -738,44 +739,56 @@ void ScatterplotPlugin::loadColors(const Dataset& pointsColor, const std // If number of points do not match, prefer checking for derived data over selection mapping if (numColorPoints != _numPoints) { - const bool hasSameNumPointsAsFull = - /*if*/ _positionDataset->isDerivedData() ? - /*then*/ _positionSourceDataset->getFullDataset()->getNumPoints() == numColorPoints : - /*else*/ false; + try { - if (hasSameNumPointsAsFull) { - std::vector globalIndices; - _positionDataset->getGlobalIndices(globalIndices); + const bool hasSameNumPointsAsFull = + /*if*/ _positionDataset->isDerivedData() ? + /*then*/ _positionSourceDataset->getFullDataset()->getNumPoints() == numColorPoints : + /*else*/ false; - std::vector localScalars(_numPoints, 0); - std::int32_t localColorIndex = 0; + if (hasSameNumPointsAsFull) { + std::vector globalIndices; + _positionDataset->getGlobalIndices(globalIndices); - for (const auto& globalIndex : globalIndices) - localScalars[localColorIndex++] = scalars[globalIndex]; + std::vector localScalars(_numPoints, 0); + std::int32_t localColorIndex = 0; - std::swap(localScalars, scalars); - } - else if ( // only get map if derived check failed - const auto selectionMapping = getSelectionMapping(pointsColor, _positionDataset); - /* check if valid */ selectionMapping.has_value() && selectionMapping.value() != nullptr - ) - { - std::vector localScalars(_numPoints, 0); + for (const auto& globalIndex : globalIndices) + localScalars[localColorIndex++] = scalars[globalIndex]; + + std::swap(localScalars, scalars); + } + else if ( // only get map if derived check failed + const auto selectionMapping = getSelectionMapping(pointsColor, _positionDataset); + /* check if valid */ selectionMapping.has_value() && selectionMapping.value() != nullptr + ) + { + std::vector localScalars(_numPoints, 0); - // Map values like selection - const mv::SelectionMap::Map& linkedMap = selectionMapping.value()->getMapping().getMap(); - const std::uint32_t numPointsInTarget = _positionDataset->getNumPoints(); + // Map values like selection + const mv::SelectionMap::Map& linkedMap = selectionMapping.value()->getMapping().getMap(); + const std::uint32_t numPointsInTarget = _positionDataset->getNumPoints(); - for (const auto& [fromID, vecOfIDs] : linkedMap) { - for (std::uint32_t toID : vecOfIDs) { - localScalars[toID] = scalars[fromID]; + for (const auto& [fromID, vecOfIDs] : linkedMap) { + for (std::uint32_t toID : vecOfIDs) { + localScalars[toID] = scalars[fromID]; + } } + + std::swap(localScalars, scalars); + } + else { + qWarning("Number of points used for coloring does not match number of points in data, aborting attempt to color plot"); + return; } - std::swap(localScalars, scalars); } - else { - qWarning("Number of points used for coloring does not match number of points in data, aborting attempt to color plot"); + catch (const std::exception& e) { + qDebug() << "ScatterplotPlugin::loadColors: mapping failed -> " << e.what(); + return; + } + catch (...) { + qDebug() << "ScatterplotPlugin::loadColors: mapping failed for an unknown reason."; return; } } From 5fc04cadc312324c998acbcf65747e8ab000d187 Mon Sep 17 00:00:00 2001 From: Alexander Vieth Date: Fri, 22 Aug 2025 14:45:50 +0200 Subject: [PATCH 05/16] Add Reverse mapping as well --- src/ScatterplotPlugin.cpp | 148 +++++++++++++++++++++++++++----------- 1 file changed, 105 insertions(+), 43 deletions(-) diff --git a/src/ScatterplotPlugin.cpp b/src/ScatterplotPlugin.cpp index 1439e67..322640c 100644 --- a/src/ScatterplotPlugin.cpp +++ b/src/ScatterplotPlugin.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -45,27 +46,30 @@ Q_PLUGIN_METADATA(IID "studio.manivault.ScatterplotPlugin") using namespace mv; using namespace mv::util; -// returns a selection map between source and target -static std::optional getSelectionMapping(const mv::Dataset& source, const mv::Dataset& target) { +// This only checks the immedeate parent and is deliberately not recursive +// We might consider the latter in the future, but might need to cover edge cases +static bool parentHasSameNumPoints(const mv::Dataset data, const mv::Dataset& other) { + if (data->isDerivedData()) { + const auto parent = data->getParent(); + if (parent->getDataType() == PointType) { + const auto parentPoints = mv::Dataset(parent); + return parentPoints->getNumPoints() == other->getNumPoints(); + } + } + return false; +} + +using CheckFunc = std::function& target)>; + +static std::optional getSelectionMapping(const mv::Dataset& source, const mv::Dataset& target, CheckFunc checkMapping) { const std::vector& linkedDatas = source->getLinkedData(); - // find linked data between source and target OR source and target's parent, if target is derived and they have the same number of points - const auto it = std::ranges::find_if(linkedDatas, [&target](const mv::LinkedData& obj) { - - // This only checks the immedeate parent and is deliberately not recursive - // We might consider the latter in the future, but would need to cover more edge cases - auto isParentOf = [&target](const mv::Dataset& linkedTarget) -> bool { - if (target->isDerivedData()) { - const auto parent = target->getParent(); - if (parent->getDataType() == PointType) { - const auto parentPoints = mv::Dataset(parent); - return parentPoints->getNumPoints() == target->getNumPoints(); - } - } - return false; - }; + if (linkedDatas.empty()) + return std::nullopt; - return obj.getTargetDataset() == target || isParentOf(obj.getTargetDataset()); + // find linked data between source and target OR source and target's parent, if target is derived and they have the same number of points + const auto it = std::ranges::find_if(linkedDatas, [&target, &checkMapping](const mv::LinkedData& linkedData) -> bool { + return checkMapping(linkedData, target); }); if (it != linkedDatas.end()) { @@ -73,20 +77,36 @@ static std::optional getSelectionMapping(const mv::Datase } return std::nullopt; // nothing found + } -// returns whether there is a selection map from source to target that covers all elements in the target -static bool checkSelectionMapping(const mv::Dataset& source, const mv::Dataset& target) { +static std::optional getSelectionMappingColorsToPositions(const mv::Dataset& colors, const mv::Dataset& positions) { + auto testTargetAndParent = [](const mv::LinkedData& linkedData, const mv::Dataset& positions) -> bool { + const Dataset mapTargetData = linkedData.getTargetDataset(); + return mapTargetData == positions || parentHasSameNumPoints(mapTargetData, positions); + }; + + return getSelectionMapping(colors, positions, testTargetAndParent); +} - // Check if there is a mapping - const auto it = getSelectionMapping(source, target); +static std::optional getSelectionMappingPositionsToColors(const mv::Dataset& positions, const mv::Dataset& colors) { + + auto testTarget = [](const mv::LinkedData& linkedData, const mv::Dataset& colors) -> bool { + return linkedData.getTargetDataset() == colors; + }; - if (!it.has_value() || it.value() == nullptr) - return false; + auto mapping = getSelectionMapping(positions, colors, testTarget); + if (!mapping.has_value() && parentHasSameNumPoints(positions, colors)) { + mapping = getSelectionMapping(positions->getParent(), colors, testTarget); + } + + return mapping; +} + +static bool checkSurjectiveMapping(const mv::LinkedData& linkedData, const std::uint32_t numPointsInTarget) { // Check if the mapping is surjective, i.e. hits all elements in the target - const std::map>& linkedMap = it.value()->getMapping().getMap(); - const std::uint32_t numPointsInTarget = target->getNumPoints(); + const std::map>& linkedMap = linkedData.getMapping().getMap(); std::vector found(numPointsInTarget, false); std::uint32_t count = 0; @@ -97,13 +117,34 @@ static bool checkSelectionMapping(const mv::Dataset& source, const mv::D if (!found[val]) { found[val] = true; - if (++count == numPointsInTarget) + if (++count == numPointsInTarget) return true; } } } - return false; // The previous loop would have returned early if the entire taget set was covered + return false; // The previous loop would have returned early if the entire taget set was covered +} + +// returns whether there is a selection map from source to target that covers all elements in the target +static bool checkSelectionMapping(const mv::Dataset& colors, const mv::Dataset& positions) { + + // Check if there is a mapping + auto mapping = getSelectionMappingColorsToPositions(colors, positions); + auto numTargetPoints = positions->getNumPoints(); + + if (!mapping.has_value() || mapping.value() == nullptr) { + + mapping = getSelectionMappingPositionsToColors(positions, colors); + numTargetPoints = colors->getNumPoints(); + + if (!mapping.has_value() || mapping.value() == nullptr) + return false; + } + + const bool mappingCoversData = checkSurjectiveMapping(*(mapping.value()), numTargetPoints); + + return mappingCoversData; } ScatterplotPlugin::ScatterplotPlugin(const PluginFactory* factory) : @@ -240,7 +281,7 @@ ScatterplotPlugin::ScatterplotPlugin(const PluginFactory* factory) : // Accept for recoloring: // 1. data with the same number of points // 2. data which is derived from a parent that has the same number of points (e.g. for HSNE embeddings), where we can use global indices for mapping - // 3. data which has a fully-covering selection mapping, that we can use for setting colors + // 3. data which has a fully-covering selection mapping to the position data (or it's parent), that we can use for setting colors // [1. Same number of points] const auto numPointsCandidate = candidateDataset->getNumPoints(); @@ -253,6 +294,9 @@ ScatterplotPlugin::ScatterplotPlugin(const PluginFactory* factory) : /*then*/ _positionDataset->getSourceDataset()->getFullDataset()->getNumPoints() == numPointsCandidate : /*else*/ false; + const auto& l1 = candidateDataset->getLinkedData(); + const auto& l2 = _positionDataset->getLinkedData(); + // [3. Full selection mapping] const bool hasSelectionMapping = checkSelectionMapping(candidateDataset, _positionDataset); @@ -729,18 +773,17 @@ void ScatterplotPlugin::loadColors(const Dataset& pointsColor, const std if (!pointsColor.isValid()) return; + const auto numColorPoints = pointsColor->getNumPoints(); + // Generate point scalars for color mapping std::vector scalars; - pointsColor->extractDataForDimension(scalars, dimensionIndex); - const auto numColorPoints = pointsColor->getNumPoints(); - - // If number of points do not match, prefer checking for derived data over selection mapping + // If number of points do not match, use a mapping + // prefer global IDs (for derived data) over selection mapping if (numColorPoints != _numPoints) { try { - const bool hasSameNumPointsAsFull = /*if*/ _positionDataset->isDerivedData() ? /*then*/ _positionSourceDataset->getFullDataset()->getNumPoints() == numColorPoints : @@ -758,24 +801,43 @@ void ScatterplotPlugin::loadColors(const Dataset& pointsColor, const std std::swap(localScalars, scalars); } - else if ( // only get map if derived check failed - const auto selectionMapping = getSelectionMapping(pointsColor, _positionDataset); + else if ( // mapping from color data set to position data set + const auto selectionMapping = getSelectionMappingColorsToPositions(pointsColor, _positionDataset); /* check if valid */ selectionMapping.has_value() && selectionMapping.value() != nullptr ) { - std::vector localScalars(_numPoints, 0); + std::vector mappedScalars(_numPoints, 0); // Map values like selection - const mv::SelectionMap::Map& linkedMap = selectionMapping.value()->getMapping().getMap(); - const std::uint32_t numPointsInTarget = _positionDataset->getNumPoints(); + const mv::SelectionMap::Map& linkedMap = selectionMapping.value()->getMapping().getMap(); - for (const auto& [fromID, vecOfIDs] : linkedMap) { - for (std::uint32_t toID : vecOfIDs) { - localScalars[toID] = scalars[fromID]; + for (const auto& [fromColorID, vecOfPositionIDs] : linkedMap) { + for (std::uint32_t toPositionID : vecOfPositionIDs) { + mappedScalars[toPositionID] = scalars[fromColorID]; } } - std::swap(localScalars, scalars); + std::swap(mappedScalars, scalars); + } + else if ( // mapping from position data set to color data set + const auto selectionMapping = getSelectionMappingPositionsToColors(_positionDataset, pointsColor); + /* check if valid */ selectionMapping.has_value() && selectionMapping.value() != nullptr + ) + { + std::vector mappedScalars(_numPoints, std::numeric_limits::lowest()); + + // Map values like selection (in reverse, use first value that occurs) + const mv::SelectionMap::Map& linkedMap = selectionMapping.value()->getMapping().getMap(); + + for (const auto& [fromPositionID, vecOfColorIDs] : linkedMap) { + if (mappedScalars[fromPositionID] != std::numeric_limits::lowest()) + continue; + for (std::uint32_t toColorID : vecOfColorIDs) { + mappedScalars[fromPositionID] = scalars[toColorID]; + } + } + + std::swap(mappedScalars, scalars); } else { qWarning("Number of points used for coloring does not match number of points in data, aborting attempt to color plot"); From 9c8361d88e48b0c959e99514e6af063015d51c63 Mon Sep 17 00:00:00 2001 From: Alexander Vieth Date: Fri, 22 Aug 2025 15:44:00 +0200 Subject: [PATCH 06/16] Fix false parent --- src/ScatterplotPlugin.cpp | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/ScatterplotPlugin.cpp b/src/ScatterplotPlugin.cpp index 322640c..11b3763 100644 --- a/src/ScatterplotPlugin.cpp +++ b/src/ScatterplotPlugin.cpp @@ -77,7 +77,6 @@ static std::optional getSelectionMapping(const mv::Datase } return std::nullopt; // nothing found - } static std::optional getSelectionMappingColorsToPositions(const mv::Dataset& colors, const mv::Dataset& positions) { @@ -90,22 +89,22 @@ static std::optional getSelectionMappingColorsToPositions } static std::optional getSelectionMappingPositionsToColors(const mv::Dataset& positions, const mv::Dataset& colors) { - auto testTarget = [](const mv::LinkedData& linkedData, const mv::Dataset& colors) -> bool { return linkedData.getTargetDataset() == colors; }; auto mapping = getSelectionMapping(positions, colors, testTarget); - if (!mapping.has_value() && parentHasSameNumPoints(positions, colors)) { - mapping = getSelectionMapping(positions->getParent(), colors, testTarget); + if (!mapping.has_value() && parentHasSameNumPoints(positions, positions)) { + const auto positionsParent = positions->getParent(); + mapping = getSelectionMapping(positionsParent, colors, testTarget); } return mapping; } +// Check if the mapping is surjective, i.e. hits all elements in the target static bool checkSurjectiveMapping(const mv::LinkedData& linkedData, const std::uint32_t numPointsInTarget) { - // Check if the mapping is surjective, i.e. hits all elements in the target const std::map>& linkedMap = linkedData.getMapping().getMap(); std::vector found(numPointsInTarget, false); @@ -126,7 +125,8 @@ static bool checkSurjectiveMapping(const mv::LinkedData& linkedData, const std:: return false; // The previous loop would have returned early if the entire taget set was covered } -// returns whether there is a selection map from source to target that covers all elements in the target +// returns whether there is a selection map from colors to positions or positions to colors (or respective parents) +// checks whether the mapping covers all elements in the target static bool checkSelectionMapping(const mv::Dataset& colors, const mv::Dataset& positions) { // Check if there is a mapping @@ -294,9 +294,6 @@ ScatterplotPlugin::ScatterplotPlugin(const PluginFactory* factory) : /*then*/ _positionDataset->getSourceDataset()->getFullDataset()->getNumPoints() == numPointsCandidate : /*else*/ false; - const auto& l1 = candidateDataset->getLinkedData(); - const auto& l2 = _positionDataset->getLinkedData(); - // [3. Full selection mapping] const bool hasSelectionMapping = checkSelectionMapping(candidateDataset, _positionDataset); From 2172eb2f21a4617962c71c42957bfb8feb9c2890 Mon Sep 17 00:00:00 2001 From: Alexander Vieth Date: Fri, 22 Aug 2025 15:58:09 +0200 Subject: [PATCH 07/16] Move new functions to own file --- CMakeLists.txt | 2 + src/MappingUtils.cpp | 110 ++++++++++++++++++++++++++++++++++++++ src/MappingUtils.h | 29 ++++++++++ src/ScatterplotPlugin.cpp | 102 +---------------------------------- 4 files changed, 142 insertions(+), 101 deletions(-) create mode 100644 src/MappingUtils.cpp create mode 100644 src/MappingUtils.h diff --git a/CMakeLists.txt b/CMakeLists.txt index c590a4d..b9674d0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,6 +40,8 @@ find_package(ManiVault COMPONENTS Core PointData ClusterData ColorData ImageData set(PLUGIN src/ScatterplotPlugin.h src/ScatterplotPlugin.cpp + src/MappingUtils.h + src/MappingUtils.cpp ) set(UI diff --git a/src/MappingUtils.cpp b/src/MappingUtils.cpp new file mode 100644 index 0000000..5af57bd --- /dev/null +++ b/src/MappingUtils.cpp @@ -0,0 +1,110 @@ +#include "MappingUtils.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +bool parentHasSameNumPoints(const mv::Dataset data, const mv::Dataset& other) { + if (data->isDerivedData()) { + const auto parent = data->getParent(); + if (parent->getDataType() == PointType) { + const auto parentPoints = mv::Dataset(parent); + return parentPoints->getNumPoints() == other->getNumPoints(); + } + } + return false; +} + +using CheckFunc = std::function& target)>; + +std::optional getSelectionMapping(const mv::Dataset& source, const mv::Dataset& target, CheckFunc checkMapping) { + const std::vector& linkedDatas = source->getLinkedData(); + + if (linkedDatas.empty()) + return std::nullopt; + + // find linked data between source and target OR source and target's parent, if target is derived and they have the same number of points + const auto it = std::ranges::find_if(linkedDatas, [&target, &checkMapping](const mv::LinkedData& linkedData) -> bool { + return checkMapping(linkedData, target); + }); + + if (it != linkedDatas.end()) { + return &(*it); // return pointer to the found object + } + + return std::nullopt; // nothing found +} + +std::optional getSelectionMappingColorsToPositions(const mv::Dataset& colors, const mv::Dataset& positions) { + auto testTargetAndParent = [](const mv::LinkedData& linkedData, const mv::Dataset& positions) -> bool { + const mv::Dataset mapTargetData = linkedData.getTargetDataset(); + return mapTargetData == positions || parentHasSameNumPoints(mapTargetData, positions); + }; + + return getSelectionMapping(colors, positions, testTargetAndParent); +} + +std::optional getSelectionMappingPositionsToColors(const mv::Dataset& positions, const mv::Dataset& colors) { + auto testTarget = [](const mv::LinkedData& linkedData, const mv::Dataset& colors) -> bool { + return linkedData.getTargetDataset() == colors; + }; + + auto mapping = getSelectionMapping(positions, colors, testTarget); + + if (!mapping.has_value() && parentHasSameNumPoints(positions, positions)) { + const auto positionsParent = positions->getParent(); + mapping = getSelectionMapping(positionsParent, colors, testTarget); + } + + return mapping; +} + +bool checkSurjectiveMapping(const mv::LinkedData& linkedData, const std::uint32_t numPointsInTarget) { + const std::map>& linkedMap = linkedData.getMapping().getMap(); + + std::vector found(numPointsInTarget, false); + std::uint32_t count = 0; + + for (const auto& [key, vec] : linkedMap) { + for (std::uint32_t val : vec) { + if (val >= numPointsInTarget) continue; // Skip values that are too large + + if (!found[val]) { + found[val] = true; + if (++count == numPointsInTarget) + return true; + } + } + } + + return false; // The previous loop would have returned early if the entire taget set was covered +} + +bool checkSelectionMapping(const mv::Dataset& colors, const mv::Dataset& positions) { + + // Check if there is a mapping + auto mapping = getSelectionMappingColorsToPositions(colors, positions); + auto numTargetPoints = positions->getNumPoints(); + + if (!mapping.has_value() || mapping.value() == nullptr) { + + mapping = getSelectionMappingPositionsToColors(positions, colors); + numTargetPoints = colors->getNumPoints(); + + if (!mapping.has_value() || mapping.value() == nullptr) + return false; + } + + const bool mappingCoversData = checkSurjectiveMapping(*(mapping.value()), numTargetPoints); + + return mappingCoversData; +} diff --git a/src/MappingUtils.h b/src/MappingUtils.h new file mode 100644 index 0000000..cda78c8 --- /dev/null +++ b/src/MappingUtils.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include +#include + +// This only checks the immedeate parent and is deliberately not recursive +// We might consider the latter in the future, but might need to cover edge cases +bool parentHasSameNumPoints(const mv::Dataset data, const mv::Dataset& other); + +using CheckFunc = std::function& target)>; + +std::optional getSelectionMapping(const mv::Dataset& source, const mv::Dataset& target, CheckFunc checkMapping); + +std::optional getSelectionMappingColorsToPositions(const mv::Dataset& colors, const mv::Dataset& positions); + +std::optional getSelectionMappingPositionsToColors(const mv::Dataset& positions, const mv::Dataset& colors); + +// Check if the mapping is surjective, i.e. hits all elements in the target +bool checkSurjectiveMapping(const mv::LinkedData& linkedData, const std::uint32_t numPointsInTarget); + +// returns whether there is a selection map from colors to positions or positions to colors (or respective parents) +// checks whether the mapping covers all elements in the target +bool checkSelectionMapping(const mv::Dataset& colors, const mv::Dataset& positions); diff --git a/src/ScatterplotPlugin.cpp b/src/ScatterplotPlugin.cpp index 11b3763..d39ba55 100644 --- a/src/ScatterplotPlugin.cpp +++ b/src/ScatterplotPlugin.cpp @@ -1,5 +1,6 @@ #include "ScatterplotPlugin.h" +#include "MappingUtils.h" #include "ScatterplotWidget.h" #include @@ -46,107 +47,6 @@ Q_PLUGIN_METADATA(IID "studio.manivault.ScatterplotPlugin") using namespace mv; using namespace mv::util; -// This only checks the immedeate parent and is deliberately not recursive -// We might consider the latter in the future, but might need to cover edge cases -static bool parentHasSameNumPoints(const mv::Dataset data, const mv::Dataset& other) { - if (data->isDerivedData()) { - const auto parent = data->getParent(); - if (parent->getDataType() == PointType) { - const auto parentPoints = mv::Dataset(parent); - return parentPoints->getNumPoints() == other->getNumPoints(); - } - } - return false; -} - -using CheckFunc = std::function& target)>; - -static std::optional getSelectionMapping(const mv::Dataset& source, const mv::Dataset& target, CheckFunc checkMapping) { - const std::vector& linkedDatas = source->getLinkedData(); - - if (linkedDatas.empty()) - return std::nullopt; - - // find linked data between source and target OR source and target's parent, if target is derived and they have the same number of points - const auto it = std::ranges::find_if(linkedDatas, [&target, &checkMapping](const mv::LinkedData& linkedData) -> bool { - return checkMapping(linkedData, target); - }); - - if (it != linkedDatas.end()) { - return &(*it); // return pointer to the found object - } - - return std::nullopt; // nothing found -} - -static std::optional getSelectionMappingColorsToPositions(const mv::Dataset& colors, const mv::Dataset& positions) { - auto testTargetAndParent = [](const mv::LinkedData& linkedData, const mv::Dataset& positions) -> bool { - const Dataset mapTargetData = linkedData.getTargetDataset(); - return mapTargetData == positions || parentHasSameNumPoints(mapTargetData, positions); - }; - - return getSelectionMapping(colors, positions, testTargetAndParent); -} - -static std::optional getSelectionMappingPositionsToColors(const mv::Dataset& positions, const mv::Dataset& colors) { - auto testTarget = [](const mv::LinkedData& linkedData, const mv::Dataset& colors) -> bool { - return linkedData.getTargetDataset() == colors; - }; - - auto mapping = getSelectionMapping(positions, colors, testTarget); - - if (!mapping.has_value() && parentHasSameNumPoints(positions, positions)) { - const auto positionsParent = positions->getParent(); - mapping = getSelectionMapping(positionsParent, colors, testTarget); - } - - return mapping; -} - -// Check if the mapping is surjective, i.e. hits all elements in the target -static bool checkSurjectiveMapping(const mv::LinkedData& linkedData, const std::uint32_t numPointsInTarget) { - const std::map>& linkedMap = linkedData.getMapping().getMap(); - - std::vector found(numPointsInTarget, false); - std::uint32_t count = 0; - - for (const auto& [key, vec] : linkedMap) { - for (std::uint32_t val : vec) { - if (val >= numPointsInTarget) continue; // Skip values that are too large - - if (!found[val]) { - found[val] = true; - if (++count == numPointsInTarget) - return true; - } - } - } - - return false; // The previous loop would have returned early if the entire taget set was covered -} - -// returns whether there is a selection map from colors to positions or positions to colors (or respective parents) -// checks whether the mapping covers all elements in the target -static bool checkSelectionMapping(const mv::Dataset& colors, const mv::Dataset& positions) { - - // Check if there is a mapping - auto mapping = getSelectionMappingColorsToPositions(colors, positions); - auto numTargetPoints = positions->getNumPoints(); - - if (!mapping.has_value() || mapping.value() == nullptr) { - - mapping = getSelectionMappingPositionsToColors(positions, colors); - numTargetPoints = colors->getNumPoints(); - - if (!mapping.has_value() || mapping.value() == nullptr) - return false; - } - - const bool mappingCoversData = checkSurjectiveMapping(*(mapping.value()), numTargetPoints); - - return mappingCoversData; -} - ScatterplotPlugin::ScatterplotPlugin(const PluginFactory* factory) : ViewPlugin(factory), _dropWidget(nullptr), From 3fda7df53a37b6c796cc8db40039cb5129aecb54 Mon Sep 17 00:00:00 2001 From: Alexander Vieth Date: Mon, 25 Aug 2025 11:46:38 +0200 Subject: [PATCH 08/16] Centralize & inline check --- src/MappingUtils.cpp | 16 ++++++++-------- src/MappingUtils.h | 20 +++++++++++++++++++- src/ScatterplotPlugin.cpp | 10 ++-------- 3 files changed, 29 insertions(+), 17 deletions(-) diff --git a/src/MappingUtils.cpp b/src/MappingUtils.cpp index 5af57bd..d494cc8 100644 --- a/src/MappingUtils.cpp +++ b/src/MappingUtils.cpp @@ -13,15 +13,15 @@ #include #include -bool parentHasSameNumPoints(const mv::Dataset data, const mv::Dataset& other) { - if (data->isDerivedData()) { - const auto parent = data->getParent(); - if (parent->getDataType() == PointType) { - const auto parentPoints = mv::Dataset(parent); - return parentPoints->getNumPoints() == other->getNumPoints(); - } +using CheckFunc = std::function& target)>; + +static void printLinkedDataNames(const mv::Dataset& data) { + const std::vector& linkedFromColors = data->getLinkedData(); + if (!linkedFromColors.empty()) { + qDebug() << data->getGuiName(); + qDebug() << linkedFromColors[0].getSourceDataSet()->getGuiName(); + qDebug() << linkedFromColors[0].getTargetDataset()->getGuiName(); } - return false; } using CheckFunc = std::function& target)>; diff --git a/src/MappingUtils.h b/src/MappingUtils.h index cda78c8..c820d38 100644 --- a/src/MappingUtils.h +++ b/src/MappingUtils.h @@ -11,7 +11,25 @@ // This only checks the immedeate parent and is deliberately not recursive // We might consider the latter in the future, but might need to cover edge cases -bool parentHasSameNumPoints(const mv::Dataset data, const mv::Dataset& other); +inline bool parentHasSameNumPoints(const mv::Dataset data, const mv::Dataset& other) { + if (!data->isDerivedData()) + return false; + + const auto parent = data->getParent(); + if (parent->getDataType() != PointType) + return false; + + const auto parentPoints = mv::Dataset(parent); + return parentPoints->getNumPoints() == other->getNumPoints(); +} + +// Is the data derived and does it's full source data have same number of points as the other data +inline bool fullSourceHasSameNumPoints(const mv::Dataset data, const mv::Dataset& other) { + if (!data->isDerivedData()) + return false; + + return data->getSourceDataset()->getFullDataset()->getNumPoints() == other->getNumPoints(); +} using CheckFunc = std::function& target)>; diff --git a/src/ScatterplotPlugin.cpp b/src/ScatterplotPlugin.cpp index d39ba55..9f5626c 100644 --- a/src/ScatterplotPlugin.cpp +++ b/src/ScatterplotPlugin.cpp @@ -189,10 +189,7 @@ ScatterplotPlugin::ScatterplotPlugin(const PluginFactory* factory) : const bool hasSameNumPoints = numPointsPosition == numPointsCandidate; // [2. Derived from a parent] - const bool hasSameNumPointsAsFull = - /*if*/ _positionDataset->isDerivedData() ? - /*then*/ _positionDataset->getSourceDataset()->getFullDataset()->getNumPoints() == numPointsCandidate : - /*else*/ false; + const bool hasSameNumPointsAsFull = fullSourceHasSameNumPoints(_positionDataset, candidateDataset); // [3. Full selection mapping] const bool hasSelectionMapping = checkSelectionMapping(candidateDataset, _positionDataset); @@ -681,10 +678,7 @@ void ScatterplotPlugin::loadColors(const Dataset& pointsColor, const std if (numColorPoints != _numPoints) { try { - const bool hasSameNumPointsAsFull = - /*if*/ _positionDataset->isDerivedData() ? - /*then*/ _positionSourceDataset->getFullDataset()->getNumPoints() == numColorPoints : - /*else*/ false; + const bool hasSameNumPointsAsFull = fullSourceHasSameNumPoints(_positionDataset, pointsColor); if (hasSameNumPointsAsFull) { std::vector globalIndices; From 5c50c7808a4c6ce4299674ca6bf4f615a8c75b21 Mon Sep 17 00:00:00 2001 From: Alexander Vieth Date: Mon, 25 Aug 2025 11:48:00 +0200 Subject: [PATCH 09/16] Simpler return types and checks --- src/MappingUtils.cpp | 53 ++++++++++++++++++++------------------- src/MappingUtils.h | 8 +++--- src/ScatterplotPlugin.cpp | 12 ++++----- 3 files changed, 37 insertions(+), 36 deletions(-) diff --git a/src/MappingUtils.cpp b/src/MappingUtils.cpp index d494cc8..c096899 100644 --- a/src/MappingUtils.cpp +++ b/src/MappingUtils.cpp @@ -9,8 +9,9 @@ #include #include #include -#include +#include #include +#include #include using CheckFunc = std::function& target)>; @@ -24,27 +25,27 @@ static void printLinkedDataNames(const mv::Dataset& data) { } } -using CheckFunc = std::function& target)>; - -std::optional getSelectionMapping(const mv::Dataset& source, const mv::Dataset& target, CheckFunc checkMapping) { +std::pair getSelectionMapping(const mv::Dataset& source, const mv::Dataset& target, CheckFunc checkMapping) { const std::vector& linkedDatas = source->getLinkedData(); if (linkedDatas.empty()) - return std::nullopt; + return { nullptr, 0 } ; // find linked data between source and target OR source and target's parent, if target is derived and they have the same number of points - const auto it = std::ranges::find_if(linkedDatas, [&target, &checkMapping](const mv::LinkedData& linkedData) -> bool { - return checkMapping(linkedData, target); - }); - - if (it != linkedDatas.end()) { - return &(*it); // return pointer to the found object + if (const auto result = std::ranges::find_if( + linkedDatas, + [&target, &checkMapping](const mv::LinkedData& linkedData) -> bool { + return checkMapping(linkedData, target); + }); + result != linkedDatas.end()) + { + return {&(*result), target->getNumPoints() }; } - return std::nullopt; // nothing found + return { nullptr, 0 }; } -std::optional getSelectionMappingColorsToPositions(const mv::Dataset& colors, const mv::Dataset& positions) { +std::pair getSelectionMappingColorsToPositions(const mv::Dataset& colors, const mv::Dataset& positions) { auto testTargetAndParent = [](const mv::LinkedData& linkedData, const mv::Dataset& positions) -> bool { const mv::Dataset mapTargetData = linkedData.getTargetDataset(); return mapTargetData == positions || parentHasSameNumPoints(mapTargetData, positions); @@ -53,19 +54,20 @@ std::optional getSelectionMappingColorsToPositions(const return getSelectionMapping(colors, positions, testTargetAndParent); } -std::optional getSelectionMappingPositionsToColors(const mv::Dataset& positions, const mv::Dataset& colors) { +std::pair getSelectionMappingPositionsToColors(const mv::Dataset& positions, const mv::Dataset& colors) { auto testTarget = [](const mv::LinkedData& linkedData, const mv::Dataset& colors) -> bool { return linkedData.getTargetDataset() == colors; }; - auto mapping = getSelectionMapping(positions, colors, testTarget); + auto [mapping, numTargetPoints] = getSelectionMapping(positions, colors, testTarget); - if (!mapping.has_value() && parentHasSameNumPoints(positions, positions)) { + if (mapping && parentHasSameNumPoints(positions, positions)) { const auto positionsParent = positions->getParent(); - mapping = getSelectionMapping(positionsParent, colors, testTarget); + std::tie(mapping, numTargetPoints) = getSelectionMapping(positionsParent, colors, testTarget); } - return mapping; + return { mapping, numTargetPoints }; +} } bool checkSurjectiveMapping(const mv::LinkedData& linkedData, const std::uint32_t numPointsInTarget) { @@ -95,16 +97,15 @@ bool checkSelectionMapping(const mv::Dataset& colors, const mv::Dataset< auto mapping = getSelectionMappingColorsToPositions(colors, positions); auto numTargetPoints = positions->getNumPoints(); - if (!mapping.has_value() || mapping.value() == nullptr) { + // Check if there is a mapping + auto [mapping, numTargetPoints] = getSelectionMappingColorsToPositions(colors, positions); - mapping = getSelectionMappingPositionsToColors(positions, colors); - numTargetPoints = colors->getNumPoints(); + if (!mapping) + std::tie(mapping, numTargetPoints) = getSelectionMappingPositionsToColors(positions, colors); - if (!mapping.has_value() || mapping.value() == nullptr) - return false; - } - const bool mappingCoversData = checkSurjectiveMapping(*(mapping.value()), numTargetPoints); + if (!mapping) + return false; - return mappingCoversData; + return checkSurjectiveMapping(*mapping, numTargetPoints); } diff --git a/src/MappingUtils.h b/src/MappingUtils.h index c820d38..cd88058 100644 --- a/src/MappingUtils.h +++ b/src/MappingUtils.h @@ -7,7 +7,7 @@ #include #include -#include +#include // This only checks the immedeate parent and is deliberately not recursive // We might consider the latter in the future, but might need to cover edge cases @@ -33,11 +33,11 @@ inline bool fullSourceHasSameNumPoints(const mv::Dataset data, using CheckFunc = std::function& target)>; -std::optional getSelectionMapping(const mv::Dataset& source, const mv::Dataset& target, CheckFunc checkMapping); +std::pair getSelectionMapping(const mv::Dataset& source, const mv::Dataset& target, CheckFunc checkMapping); -std::optional getSelectionMappingColorsToPositions(const mv::Dataset& colors, const mv::Dataset& positions); +std::pair getSelectionMappingColorsToPositions(const mv::Dataset& colors, const mv::Dataset& positions); -std::optional getSelectionMappingPositionsToColors(const mv::Dataset& positions, const mv::Dataset& colors); +std::pair getSelectionMappingPositionsToColors(const mv::Dataset& positions, const mv::Dataset& colors); // Check if the mapping is surjective, i.e. hits all elements in the target bool checkSurjectiveMapping(const mv::LinkedData& linkedData, const std::uint32_t numPointsInTarget); diff --git a/src/ScatterplotPlugin.cpp b/src/ScatterplotPlugin.cpp index 9f5626c..91cffcf 100644 --- a/src/ScatterplotPlugin.cpp +++ b/src/ScatterplotPlugin.cpp @@ -693,14 +693,14 @@ void ScatterplotPlugin::loadColors(const Dataset& pointsColor, const std std::swap(localScalars, scalars); } else if ( // mapping from color data set to position data set - const auto selectionMapping = getSelectionMappingColorsToPositions(pointsColor, _positionDataset); - /* check if valid */ selectionMapping.has_value() && selectionMapping.value() != nullptr + const auto [selectionMapping, numPointsTarget] = getSelectionMappingColorsToPositions(pointsColor, _positionDataset); + /* check if valid */ selectionMapping != nullptr // && numPointsTarget == _numPoints ) { std::vector mappedScalars(_numPoints, 0); // Map values like selection - const mv::SelectionMap::Map& linkedMap = selectionMapping.value()->getMapping().getMap(); + const mv::SelectionMap::Map& linkedMap = selectionMapping->getMapping().getMap(); for (const auto& [fromColorID, vecOfPositionIDs] : linkedMap) { for (std::uint32_t toPositionID : vecOfPositionIDs) { @@ -711,14 +711,14 @@ void ScatterplotPlugin::loadColors(const Dataset& pointsColor, const std std::swap(mappedScalars, scalars); } else if ( // mapping from position data set to color data set - const auto selectionMapping = getSelectionMappingPositionsToColors(_positionDataset, pointsColor); - /* check if valid */ selectionMapping.has_value() && selectionMapping.value() != nullptr + const auto [selectionMapping, numPointsTarget] = getSelectionMappingPositionsToColors(_positionDataset, pointsColor); + /* check if valid */ selectionMapping != nullptr // && numPointsTarget == _numPoints ) { std::vector mappedScalars(_numPoints, std::numeric_limits::lowest()); // Map values like selection (in reverse, use first value that occurs) - const mv::SelectionMap::Map& linkedMap = selectionMapping.value()->getMapping().getMap(); + const mv::SelectionMap::Map& linkedMap = selectionMapping->getMapping().getMap(); for (const auto& [fromPositionID, vecOfColorIDs] : linkedMap) { if (mappedScalars[fromPositionID] != std::numeric_limits::lowest()) From c376c601fbb33beb04981d8247a88881d84c8080 Mon Sep 17 00:00:00 2001 From: Alexander Vieth Date: Mon, 25 Aug 2025 11:48:22 +0200 Subject: [PATCH 10/16] Single swap --- src/ScatterplotPlugin.cpp | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/ScatterplotPlugin.cpp b/src/ScatterplotPlugin.cpp index 91cffcf..b6f5baf 100644 --- a/src/ScatterplotPlugin.cpp +++ b/src/ScatterplotPlugin.cpp @@ -677,6 +677,8 @@ void ScatterplotPlugin::loadColors(const Dataset& pointsColor, const std // prefer global IDs (for derived data) over selection mapping if (numColorPoints != _numPoints) { + std::vector mappedScalars(_numPoints, std::numeric_limits::lowest()); + try { const bool hasSameNumPointsAsFull = fullSourceHasSameNumPoints(_positionDataset, pointsColor); @@ -684,21 +686,16 @@ void ScatterplotPlugin::loadColors(const Dataset& pointsColor, const std std::vector globalIndices; _positionDataset->getGlobalIndices(globalIndices); - std::vector localScalars(_numPoints, 0); - std::int32_t localColorIndex = 0; - - for (const auto& globalIndex : globalIndices) - localScalars[localColorIndex++] = scalars[globalIndex]; + for (std::int32_t localColorIndex = 0; localColorIndex < globalIndices.size(); localColorIndex++) { + mappedScalars[localColorIndex] = scalars[globalIndices[localColorIndex]]; + } - std::swap(localScalars, scalars); } else if ( // mapping from color data set to position data set const auto [selectionMapping, numPointsTarget] = getSelectionMappingColorsToPositions(pointsColor, _positionDataset); /* check if valid */ selectionMapping != nullptr // && numPointsTarget == _numPoints ) { - std::vector mappedScalars(_numPoints, 0); - // Map values like selection const mv::SelectionMap::Map& linkedMap = selectionMapping->getMapping().getMap(); @@ -708,15 +705,13 @@ void ScatterplotPlugin::loadColors(const Dataset& pointsColor, const std } } - std::swap(mappedScalars, scalars); } else if ( // mapping from position data set to color data set const auto [selectionMapping, numPointsTarget] = getSelectionMappingPositionsToColors(_positionDataset, pointsColor); /* check if valid */ selectionMapping != nullptr // && numPointsTarget == _numPoints ) { - std::vector mappedScalars(_numPoints, std::numeric_limits::lowest()); - + // Map values like selection (in reverse, use first value that occurs) const mv::SelectionMap::Map& linkedMap = selectionMapping->getMapping().getMap(); @@ -728,13 +723,13 @@ void ScatterplotPlugin::loadColors(const Dataset& pointsColor, const std } } - std::swap(mappedScalars, scalars); } else { qWarning("Number of points used for coloring does not match number of points in data, aborting attempt to color plot"); return; } + std::swap(mappedScalars, scalars); } catch (const std::exception& e) { qDebug() << "ScatterplotPlugin::loadColors: mapping failed -> " << e.what(); From c6e0fe844a0f6650f4c372f4da6ad3cf7415e2ee Mon Sep 17 00:00:00 2001 From: Alexander Vieth Date: Mon, 25 Aug 2025 11:48:42 +0200 Subject: [PATCH 11/16] WIP: map means to HSNE embedding --- src/MappingUtils.cpp | 18 +++++++++++++++--- src/MappingUtils.h | 2 ++ src/ScatterplotPlugin.cpp | 27 +++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/src/MappingUtils.cpp b/src/MappingUtils.cpp index c096899..03b98ad 100644 --- a/src/MappingUtils.cpp +++ b/src/MappingUtils.cpp @@ -68,6 +68,17 @@ std::pair getSelectionMappingPositionsToCol return { mapping, numTargetPoints }; } + +std::pair getSelectionMappingPositionSourceToColors(const mv::Dataset& positions, const mv::Dataset& colors) { + if (!positions->isDerivedData()) + return { nullptr, 0 }; + + const auto fullSourceData = positions->getSourceDataset()->getFullDataset(); + + if(!fullSourceData.isValid()) + return { nullptr, 0 }; + + return getSelectionMappingPositionsToColors(fullSourceData, colors); } bool checkSurjectiveMapping(const mv::LinkedData& linkedData, const std::uint32_t numPointsInTarget) { @@ -93,9 +104,8 @@ bool checkSurjectiveMapping(const mv::LinkedData& linkedData, const std::uint32_ bool checkSelectionMapping(const mv::Dataset& colors, const mv::Dataset& positions) { - // Check if there is a mapping - auto mapping = getSelectionMappingColorsToPositions(colors, positions); - auto numTargetPoints = positions->getNumPoints(); + printLinkedDataNames(colors); + printLinkedDataNames(positions); // Check if there is a mapping auto [mapping, numTargetPoints] = getSelectionMappingColorsToPositions(colors, positions); @@ -103,6 +113,8 @@ bool checkSelectionMapping(const mv::Dataset& colors, const mv::Dataset< if (!mapping) std::tie(mapping, numTargetPoints) = getSelectionMappingPositionsToColors(positions, colors); + if (!mapping) + std::tie(mapping, numTargetPoints) = getSelectionMappingPositionSourceToColors(positions, colors); if (!mapping) return false; diff --git a/src/MappingUtils.h b/src/MappingUtils.h index cd88058..1626231 100644 --- a/src/MappingUtils.h +++ b/src/MappingUtils.h @@ -39,6 +39,8 @@ std::pair getSelectionMappingColorsToPositi std::pair getSelectionMappingPositionsToColors(const mv::Dataset& positions, const mv::Dataset& colors); +std::pair getSelectionMappingPositionSourceToColors(const mv::Dataset& positions, const mv::Dataset& colors); + // Check if the mapping is surjective, i.e. hits all elements in the target bool checkSurjectiveMapping(const mv::LinkedData& linkedData, const std::uint32_t numPointsInTarget); diff --git a/src/ScatterplotPlugin.cpp b/src/ScatterplotPlugin.cpp index b6f5baf..700c496 100644 --- a/src/ScatterplotPlugin.cpp +++ b/src/ScatterplotPlugin.cpp @@ -723,6 +723,33 @@ void ScatterplotPlugin::loadColors(const Dataset& pointsColor, const std } } + } + else if ( // mapping from position data set to color data set + const auto [selectionMapping, numPointsTarget] = getSelectionMappingPositionSourceToColors(_positionDataset, pointsColor); + /* check if valid */ selectionMapping != nullptr // && numPointsTarget == _numPoints + ) + { // THIS DOES NOT WORK YET + + // selectionMapping is from full source data (scVI) to pointsColor (means) + // we need to use both the global indices and linked data mapping + + const mv::SelectionMap::Map& linkedMap = selectionMapping->getMapping().getMap(); + + std::vector globalIndices; + _positionDataset->getGlobalIndices(globalIndices); + + for (std::int32_t localColorIndex = 0; localColorIndex < globalIndices.size(); localColorIndex++) { + + const auto& mappedIndices = linkedMap.at(globalIndices[localColorIndex]); // from full source (parent) to means + + for (const auto& mappedIndex : mappedIndices) { + if (mappedScalars[mappedIndex] != std::numeric_limits::lowest()) + continue; + + mappedScalars[localColorIndex] = scalars[mappedIndex]; + } + } + } else { qWarning("Number of points used for coloring does not match number of points in data, aborting attempt to color plot"); From b968ef3af423354cc9665c6cf913e31c9f375126 Mon Sep 17 00:00:00 2001 From: Alexander Vieth Date: Mon, 25 Aug 2025 13:49:32 +0200 Subject: [PATCH 12/16] Fix mapping from source of position to color by linked data --- src/ScatterplotPlugin.cpp | 81 +++++++++++++++++++-------------------- 1 file changed, 39 insertions(+), 42 deletions(-) diff --git a/src/ScatterplotPlugin.cpp b/src/ScatterplotPlugin.cpp index 700c496..24ce33e 100644 --- a/src/ScatterplotPlugin.cpp +++ b/src/ScatterplotPlugin.cpp @@ -33,10 +33,8 @@ #include #include #include -#include #include -#include -#include +#include #include #define VIEW_SAMPLING_HTML @@ -669,109 +667,108 @@ void ScatterplotPlugin::loadColors(const Dataset& pointsColor, const std const auto numColorPoints = pointsColor->getNumPoints(); - // Generate point scalars for color mapping - std::vector scalars; - pointsColor->extractDataForDimension(scalars, dimensionIndex); + // Generate point colorScalars for color mapping + std::vector colorScalars = {}; + pointsColor->extractDataForDimension(colorScalars, dimensionIndex); // If number of points do not match, use a mapping // prefer global IDs (for derived data) over selection mapping if (numColorPoints != _numPoints) { - std::vector mappedScalars(_numPoints, std::numeric_limits::lowest()); + std::vector mappedColorScalars(_numPoints, std::numeric_limits::lowest()); try { const bool hasSameNumPointsAsFull = fullSourceHasSameNumPoints(_positionDataset, pointsColor); if (hasSameNumPointsAsFull) { - std::vector globalIndices; + std::vector globalIndices = {}; _positionDataset->getGlobalIndices(globalIndices); - for (std::int32_t localColorIndex = 0; localColorIndex < globalIndices.size(); localColorIndex++) { - mappedScalars[localColorIndex] = scalars[globalIndices[localColorIndex]]; + for (std::int32_t localIndex = 0; localIndex < globalIndices.size(); localIndex++) { + mappedColorScalars[localIndex] = colorScalars[globalIndices[localIndex]]; } } else if ( // mapping from color data set to position data set const auto [selectionMapping, numPointsTarget] = getSelectionMappingColorsToPositions(pointsColor, _positionDataset); - /* check if valid */ selectionMapping != nullptr // && numPointsTarget == _numPoints + /* check if valid */ selectionMapping != nullptr && numPointsTarget == _numPoints ) { // Map values like selection - const mv::SelectionMap::Map& linkedMap = selectionMapping->getMapping().getMap(); + const mv::SelectionMap::Map& mapColorsToPositions = selectionMapping->getMapping().getMap(); - for (const auto& [fromColorID, vecOfPositionIDs] : linkedMap) { + for (const auto& [fromColorID, vecOfPositionIDs] : mapColorsToPositions) { for (std::uint32_t toPositionID : vecOfPositionIDs) { - mappedScalars[toPositionID] = scalars[fromColorID]; + mappedColorScalars[toPositionID] = colorScalars[fromColorID]; } } } else if ( // mapping from position data set to color data set const auto [selectionMapping, numPointsTarget] = getSelectionMappingPositionsToColors(_positionDataset, pointsColor); - /* check if valid */ selectionMapping != nullptr // && numPointsTarget == _numPoints + /* check if valid */ selectionMapping != nullptr && numPointsTarget == numColorPoints ) { // Map values like selection (in reverse, use first value that occurs) - const mv::SelectionMap::Map& linkedMap = selectionMapping->getMapping().getMap(); + const mv::SelectionMap::Map& mapPositionsToColors = selectionMapping->getMapping().getMap(); - for (const auto& [fromPositionID, vecOfColorIDs] : linkedMap) { - if (mappedScalars[fromPositionID] != std::numeric_limits::lowest()) + for (const auto& [fromPositionID, vecOfColorIDs] : mapPositionsToColors) { + if (mappedColorScalars[fromPositionID] != std::numeric_limits::lowest()) continue; for (std::uint32_t toColorID : vecOfColorIDs) { - mappedScalars[fromPositionID] = scalars[toColorID]; + mappedColorScalars[fromPositionID] = colorScalars[toColorID]; } } } - else if ( // mapping from position data set to color data set + else if ( // mapping from source of position data set to color data set const auto [selectionMapping, numPointsTarget] = getSelectionMappingPositionSourceToColors(_positionDataset, pointsColor); - /* check if valid */ selectionMapping != nullptr // && numPointsTarget == _numPoints + /* check if valid */ selectionMapping != nullptr && numPointsTarget == numColorPoints ) - { // THIS DOES NOT WORK YET - - // selectionMapping is from full source data (scVI) to pointsColor (means) - // we need to use both the global indices and linked data mapping - - const mv::SelectionMap::Map& linkedMap = selectionMapping->getMapping().getMap(); - - std::vector globalIndices; + { + // the selection map is from full source data of positions data to pointsColor + // we need to use both the global indices of the positions (i.e. in the source) and the linked data mapping + const mv::SelectionMap::Map& mapGlobalToColors = selectionMapping->getMapping().getMap(); + std::vector globalIndices = {}; _positionDataset->getGlobalIndices(globalIndices); - for (std::int32_t localColorIndex = 0; localColorIndex < globalIndices.size(); localColorIndex++) { + for (std::int32_t localIndex = 0; localIndex < globalIndices.size(); localIndex++) { - const auto& mappedIndices = linkedMap.at(globalIndices[localColorIndex]); // from full source (parent) to means + if (mappedColorScalars[localIndex] != std::numeric_limits::lowest()) + continue; - for (const auto& mappedIndex : mappedIndices) { - if (mappedScalars[mappedIndex] != std::numeric_limits::lowest()) - continue; + const auto& indxColors = mapGlobalToColors.at(globalIndices[localIndex]); // from full source (parent) to colorDataset - mappedScalars[localColorIndex] = scalars[mappedIndex]; + for (const auto& indColors : indxColors) { + mappedColorScalars[localIndex] = colorScalars[indColors]; } } } else { - qWarning("Number of points used for coloring does not match number of points in data, aborting attempt to color plot"); - return; + throw std::runtime_error("Coloring data set does not match position data set in a known way, aborting attempt to color plot"); } - std::swap(mappedScalars, scalars); } catch (const std::exception& e) { - qDebug() << "ScatterplotPlugin::loadColors: mapping failed -> " << e.what(); + qDebug() << "ScatterplotPlugin::loadColors: mapping failed -> " << e.what(); + _settingsAction.getColoringAction().getColorByAction().setCurrentIndex(0); // reset to color by constant return; } catch (...) { qDebug() << "ScatterplotPlugin::loadColors: mapping failed for an unknown reason."; + _settingsAction.getColoringAction().getColorByAction().setCurrentIndex(0); // reset to color by constant return; } + + std::swap(mappedColorScalars, colorScalars); } - assert(scalars.size() == _numPoints); + assert(colorScalars.size() == _numPoints); - // Assign scalars and scalar effect - _scatterPlotWidget->setScalars(scalars); + // Assign colorScalars and scalar effect + _scatterPlotWidget->setScalars(colorScalars); _scatterPlotWidget->setScalarEffect(PointEffect::Color); _settingsAction.getColoringAction().updateColorMapActionScalarRange(); From 20750f219a1e9fb4c8ae4c6afb39215711b55e2b Mon Sep 17 00:00:00 2001 From: Alexander Vieth Date: Mon, 25 Aug 2025 13:50:15 +0200 Subject: [PATCH 13/16] Remove debug prints --- src/MappingUtils.cpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/MappingUtils.cpp b/src/MappingUtils.cpp index 03b98ad..45e5118 100644 --- a/src/MappingUtils.cpp +++ b/src/MappingUtils.cpp @@ -16,15 +16,6 @@ using CheckFunc = std::function& target)>; -static void printLinkedDataNames(const mv::Dataset& data) { - const std::vector& linkedFromColors = data->getLinkedData(); - if (!linkedFromColors.empty()) { - qDebug() << data->getGuiName(); - qDebug() << linkedFromColors[0].getSourceDataSet()->getGuiName(); - qDebug() << linkedFromColors[0].getTargetDataset()->getGuiName(); - } -} - std::pair getSelectionMapping(const mv::Dataset& source, const mv::Dataset& target, CheckFunc checkMapping) { const std::vector& linkedDatas = source->getLinkedData(); @@ -104,9 +95,6 @@ bool checkSurjectiveMapping(const mv::LinkedData& linkedData, const std::uint32_ bool checkSelectionMapping(const mv::Dataset& colors, const mv::Dataset& positions) { - printLinkedDataNames(colors); - printLinkedDataNames(positions); - // Check if there is a mapping auto [mapping, numTargetPoints] = getSelectionMappingColorsToPositions(colors, positions); From d4550613510e3066af637ee902b82c67b6b8713e Mon Sep 17 00:00:00 2001 From: Alexander Vieth Date: Mon, 25 Aug 2025 14:04:33 +0200 Subject: [PATCH 14/16] Delay surjection check for faster UI response --- src/MappingUtils.cpp | 2 +- src/ScatterplotPlugin.cpp | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/MappingUtils.cpp b/src/MappingUtils.cpp index 45e5118..839de6e 100644 --- a/src/MappingUtils.cpp +++ b/src/MappingUtils.cpp @@ -107,5 +107,5 @@ bool checkSelectionMapping(const mv::Dataset& colors, const mv::Dataset< if (!mapping) return false; - return checkSurjectiveMapping(*mapping, numTargetPoints); + return true; } diff --git a/src/ScatterplotPlugin.cpp b/src/ScatterplotPlugin.cpp index 24ce33e..0a73ffa 100644 --- a/src/ScatterplotPlugin.cpp +++ b/src/ScatterplotPlugin.cpp @@ -691,7 +691,10 @@ void ScatterplotPlugin::loadColors(const Dataset& pointsColor, const std } else if ( // mapping from color data set to position data set const auto [selectionMapping, numPointsTarget] = getSelectionMappingColorsToPositions(pointsColor, _positionDataset); - /* check if valid */ selectionMapping != nullptr && numPointsTarget == _numPoints + /* check if valid */ + selectionMapping != nullptr && + numPointsTarget == _numPoints && + checkSurjectiveMapping(*selectionMapping, numPointsTarget) ) { // Map values like selection @@ -706,10 +709,12 @@ void ScatterplotPlugin::loadColors(const Dataset& pointsColor, const std } else if ( // mapping from position data set to color data set const auto [selectionMapping, numPointsTarget] = getSelectionMappingPositionsToColors(_positionDataset, pointsColor); - /* check if valid */ selectionMapping != nullptr && numPointsTarget == numColorPoints + /* check if valid */ + selectionMapping != nullptr && + numPointsTarget == numColorPoints && + checkSurjectiveMapping(*selectionMapping, numPointsTarget) ) { - // Map values like selection (in reverse, use first value that occurs) const mv::SelectionMap::Map& mapPositionsToColors = selectionMapping->getMapping().getMap(); @@ -724,7 +729,10 @@ void ScatterplotPlugin::loadColors(const Dataset& pointsColor, const std } else if ( // mapping from source of position data set to color data set const auto [selectionMapping, numPointsTarget] = getSelectionMappingPositionSourceToColors(_positionDataset, pointsColor); - /* check if valid */ selectionMapping != nullptr && numPointsTarget == numColorPoints + /* check if valid */ + selectionMapping != nullptr && + numPointsTarget == numColorPoints && + checkSurjectiveMapping(*selectionMapping, numPointsTarget) ) { // the selection map is from full source data of positions data to pointsColor From ec72f007df8f0e74bb3fd1857bd4777003c406a0 Mon Sep 17 00:00:00 2001 From: Alexander Vieth Date: Mon, 25 Aug 2025 14:09:28 +0200 Subject: [PATCH 15/16] update doc comments [skip ci] --- src/ScatterplotPlugin.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/ScatterplotPlugin.cpp b/src/ScatterplotPlugin.cpp index 0a73ffa..c1b8b4a 100644 --- a/src/ScatterplotPlugin.cpp +++ b/src/ScatterplotPlugin.cpp @@ -179,7 +179,10 @@ ScatterplotPlugin::ScatterplotPlugin(const PluginFactory* factory) : // Accept for recoloring: // 1. data with the same number of points // 2. data which is derived from a parent that has the same number of points (e.g. for HSNE embeddings), where we can use global indices for mapping - // 3. data which has a fully-covering selection mapping to the position data (or it's parent), that we can use for setting colors + // 3. data which has a fully-covering selection mapping, that we can use for setting colors. Mapping in order of preference: + // a) from color (or it's parent) to position + // b) from color to position (or it's parent) + // c) from source of position to color // [1. Same number of points] const auto numPointsCandidate = candidateDataset->getNumPoints(); @@ -201,7 +204,6 @@ ScatterplotPlugin::ScatterplotPlugin(const PluginFactory* factory) : } // Accept for resizing and opacity: Only data with the same number of points - if (hasSameNumPoints) { // Offer the option to use the points dataset as source for points size dropRegions << new DropWidget::DropRegion(this, "Point size", QString("Size %1 points with %2").arg(_positionDataset->text(), candidateDataset->text()), "ruler-horizontal", true, [this, candidateDataset]() { @@ -673,6 +675,7 @@ void ScatterplotPlugin::loadColors(const Dataset& pointsColor, const std // If number of points do not match, use a mapping // prefer global IDs (for derived data) over selection mapping + // prefer color to position over position to color over source of position to color if (numColorPoints != _numPoints) { std::vector mappedColorScalars(_numPoints, std::numeric_limits::lowest()); From cc0923d966b1f897eb71c0d378a3234ab5291dcf Mon Sep 17 00:00:00 2001 From: Alexander Vieth Date: Mon, 25 Aug 2025 14:22:28 +0200 Subject: [PATCH 16/16] add more docs [skip ci] --- src/MappingUtils.cpp | 4 +--- src/MappingUtils.h | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/MappingUtils.cpp b/src/MappingUtils.cpp index 839de6e..e238064 100644 --- a/src/MappingUtils.cpp +++ b/src/MappingUtils.cpp @@ -14,9 +14,7 @@ #include #include -using CheckFunc = std::function& target)>; - -std::pair getSelectionMapping(const mv::Dataset& source, const mv::Dataset& target, CheckFunc checkMapping) { +std::pair getSelectionMapping(const mv::Dataset& source, const mv::Dataset& target, LinkedDataCondition checkMapping) { const std::vector& linkedDatas = source->getLinkedData(); if (linkedDatas.empty()) diff --git a/src/MappingUtils.h b/src/MappingUtils.h index 1626231..62a4cba 100644 --- a/src/MappingUtils.h +++ b/src/MappingUtils.h @@ -31,14 +31,24 @@ inline bool fullSourceHasSameNumPoints(const mv::Dataset data, return data->getSourceDataset()->getFullDataset()->getNumPoints() == other->getNumPoints(); } -using CheckFunc = std::function& target)>; +using LinkedDataCondition = std::function& target)>; -std::pair getSelectionMapping(const mv::Dataset& source, const mv::Dataset& target, CheckFunc checkMapping); +/* Returns a mapping (linked data) from source that fulfils a given condition based on target, e.g. + auto checkMapping = [](const mv::LinkedData& linkedData, const mv::Dataset& target) -> bool { + return linkedData.getTargetDataset() == target; + }; + This function will return the first match of the condition +*/ +std::pair getSelectionMapping(const mv::Dataset& source, const mv::Dataset& target, LinkedDataCondition checkMapping); +// Returns a mapping (linked data) from colors whose target is positions or whose target's parent has the same number of points as positions std::pair getSelectionMappingColorsToPositions(const mv::Dataset& colors, const mv::Dataset& positions); +// Returns a mapping (linked data) from positions whose target is colors or +// a mapping from positions' parent whose target is colors if the number of data points match std::pair getSelectionMappingPositionsToColors(const mv::Dataset& positions, const mv::Dataset& colors); +// Returns a mapping (linked data) from positions' source data whose target is colors std::pair getSelectionMappingPositionSourceToColors(const mv::Dataset& positions, const mv::Dataset& colors); // Check if the mapping is surjective, i.e. hits all elements in the target