diff --git a/CMake/merge_tree_planar_layout.xml b/CMake/merge_tree_planar_layout.xml index bf4b57cd3f..333b3035d1 100644 --- a/CMake/merge_tree_planar_layout.xml +++ b/CMake/merge_tree_planar_layout.xml @@ -17,6 +17,25 @@ panel_visibility="advanced"> + + + + + + + + + + + diff --git a/CMake/merge_tree_preprocess.xml b/CMake/merge_tree_preprocess.xml index 4b7c7bfc53..56e3cabc9a 100644 --- a/CMake/merge_tree_preprocess.xml +++ b/CMake/merge_tree_preprocess.xml @@ -20,6 +20,14 @@ panel_visibility="advanced"> mode="visibility" property="Backend" value="2" /> + + @@ -81,6 +89,14 @@ default_values="5"> mode="visibility" property="Backend" value="2" /> + + @@ -177,6 +193,14 @@ default_values="0"> mode="visibility" property="Backend" value="2" /> + + diff --git a/CMakePresets.json b/CMakePresets.json index 732d7dd1aa..d720fb236f 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -1,41 +1,61 @@ { - "version": 2, - "configurePresets": [ - { - "name": "TTK-Default", - "hidden": true, - "binaryDir": "build", - "generator": "Ninja", - "cacheVariables": { - "TTK_ENABLE_DOUBLE_TEMPLATING": "ON" - } - }, - { - "name": "TTK-Release", - "inherits": "TTK-Default", - "cacheVariables": { - "CMAKE_BUILD_TYPE": "Release", - "TTK_ENABLE_CPU_OPTIMIZATION": "OFF", - "TTK_ENABLE_MPI": "OFF", - "TTK_ENABLE_KAMIKAZE": "ON" - } - }, - { - "name": "TTK-PerformanceBenchmark", - "inherits": "TTK-Default", - "cacheVariables": { - "CMAKE_BUILD_TYPE": "Release", - "TTK_ENABLE_CPU_OPTIMIZATION": "ON", - "TTK_ENABLE_KAMIKAZE": "ON" - } - }, - { - "name": "TTK-Debug", - "inherits": "TTK-Default", - "cacheVariables": { - "CMAKE_BUILD_TYPE": "Debug", - "TTK_ENABLE_KAMIKAZE": "OFF" - } - } - ] -} + "version": 6, + "configurePresets": [ + { + "name": "TTK-Default", + "hidden": true, + "binaryDir": "build", + "generator": "Unix Makefiles", + "cacheVariables": { + "TTK_ENABLE_DOUBLE_TEMPLATING": "OFF" + } + }, + { + "name": "TTK-Release", + "inherits": "TTK-Default", + "binaryDir": "build/release", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "TTK_ENABLE_CPU_OPTIMIZATION": "OFF", + "TTK_ENABLE_MPI": "OFF", + "TTK_ENABLE_KAMIKAZE": "ON", + "TTK_ENABLE_WEBSOCKETPP": "OFF", + "WEBSOCKETPP_DIR": "/home/wetzels/ttk/ttk-jonas/websocketpp/install/lib/cmake/websocketpp" + } + }, + { + "name": "TTK-PerformanceBenchmark", + "inherits": "TTK-Default", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "TTK_ENABLE_CPU_OPTIMIZATION": "ON", + "TTK_ENABLE_KAMIKAZE": "ON" + } + }, + { + "name": "TTK-Debug", + "inherits": "TTK-Default", + "binaryDir": "build/debug", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "TTK_ENABLE_KAMIKAZE": "OFF" + } + } + ], + "buildPresets": [ + { + "name": "TTK-Release", + "description": "", + "displayName": "", + "configurePreset": "TTK-Release", + "jobs": 4 + }, + { + "name": "TTK-Debug", + "description": "", + "displayName": "", + "configurePreset": "TTK-Debug", + "jobs": 4 + } + ] +} \ No newline at end of file diff --git a/core/base/ftmTree/FTMTreeUtils_Template.h b/core/base/ftmTree/FTMTreeUtils_Template.h index 119befe4d0..587cb492f0 100644 --- a/core/base/ftmTree/FTMTreeUtils_Template.h +++ b/core/base/ftmTree/FTMTreeUtils_Template.h @@ -39,21 +39,63 @@ namespace ttk { double threshold, std::vector &excludeLower, std::vector &excludeHigher) { - dataType rootPers = this->getNodePersistence(this->getRoot()); - if(threshold > 1) - threshold /= 100.0; + idNode treeRoot = this->getRoot(); + dataType rootValue = this->getValue(treeRoot); + dataType lowestNodeValue + = this->getValue(this->getLowestNode(treeRoot)); + dataType rootPers + = (rootValue > lowestNodeValue ? rootValue - lowestNodeValue + : lowestNodeValue - rootValue); + threshold /= 100.0; threshold = rootPers * threshold; - auto pers = this->getNodePersistence(nodeId); - - // Excluded pairs - bool isExcluded = false; - if(excludeLower.size() == excludeHigher.size()) - for(unsigned i = 0; i < excludeLower.size(); ++i) { - isExcluded |= (pers > rootPers * excludeLower[i] / 100.0 - and pers < rootPers * excludeHigher[i] / 100.0); - } - return pers > threshold and not isExcluded; + auto isImportantPairOneNode = [&](idNode node) { + auto pers = this->getNodePersistence(node); + + // Excluded pairs + bool isExcluded = false; + if(excludeLower.size() == excludeHigher.size()) + for(unsigned i = 0; i < excludeLower.size(); ++i) { + isExcluded |= (pers > rootPers * excludeLower[i] / 100.0 + and pers < rootPers * excludeHigher[i] / 100.0); + } + return (pers > threshold and not isExcluded); + }; + + if(isImportantPairOneNode(nodeId)) + return true; + + // Test if it is a parent of an important pair (for not persistence based + // branch decomposition) + idNode saddleNode + = (this->isLeaf(nodeId) ? this->getNode(nodeId)->getOrigin() : nodeId); + idNode leafNode + = (this->isLeaf(nodeId) ? nodeId : this->getNode(nodeId)->getOrigin()); + std::queue queue; + std::vector nodeDone(this->getNumberOfNodes(), false); + queue.emplace(leafNode); + while(!queue.empty()) { + idNode node = queue.front(); + queue.pop(); + + if(isImportantPairOneNode(node)) + return true; + + nodeDone[node] = true; + + idNode parent = this->getParentSafe(node); + if(parent != saddleNode) + if(not nodeDone[parent]) + queue.emplace(parent); + + std::vector children; + this->getChildren(node, children); + for(auto child : children) + if(not nodeDone[child]) + queue.emplace(child); + } + + return false; } template diff --git a/core/base/mergeTreeClustering/BranchMappingDistance.h b/core/base/mergeTreeClustering/BranchMappingDistance.h index ceb9d1d932..de3e47c835 100644 --- a/core/base/mergeTreeClustering/BranchMappingDistance.h +++ b/core/base/mergeTreeClustering/BranchMappingDistance.h @@ -28,6 +28,7 @@ #include // ttk common includes +#include "MergeTreeBase.h" #include #include #include @@ -36,12 +37,17 @@ namespace ttk { - class BranchMappingDistance : virtual public Debug { + class BranchMappingDistance : virtual public Debug, public MergeTreeBase { private: int baseMetric_ = 0; int assignmentSolverID_ = 0; bool squared_ = false; + bool computeMapping_ = false; + bool writeOptimalBranchDecomposition_ = false; + + bool preprocess_ = true; + bool saveTree_ = false; template inline dataType editCost_Wasserstein1(int n1, @@ -193,11 +199,66 @@ namespace ttk { squared_ = s; } + void setComputeMapping(bool m) { + computeMapping_ = m; + } + + void setWriteBD(bool w) { + writeOptimalBranchDecomposition_ = w; + } + + void setPreprocess(bool p) { + preprocess_ = p; + } + + void setSaveTree(bool save) { + saveTree_ = save; + } + template - dataType editDistance_branch(ftm::FTMTree_MT *tree1, - ftm::FTMTree_MT *tree2) { + dataType execute( + ftm::MergeTree &mTree1, + ftm::MergeTree &mTree2, + std::vector> *outputMatching + = nullptr) { - // initialize memoization tables + ftm::MergeTree mTree1Copy; + ftm::MergeTree mTree2Copy; + if(saveTree_) { + mTree1Copy = ftm::copyMergeTree(mTree1); + mTree2Copy = ftm::copyMergeTree(mTree2); + } + ftm::MergeTree &mTree1Int = (saveTree_ ? mTree1Copy : mTree1); + ftm::MergeTree &mTree2Int = (saveTree_ ? mTree2Copy : mTree2); + ftm::FTMTree_MT *tree1 = &(mTree1Int.tree); + ftm::FTMTree_MT *tree2 = &(mTree2Int.tree); + + // optional preprocessing + if(preprocess_) { + treesNodeCorr_.resize(2); + preprocessingPipeline( + mTree1Int, epsilonTree1_, epsilon2Tree1_, epsilon3Tree1_, false, + useMinMaxPair_, cleanTree_, treesNodeCorr_[0], true, true); + preprocessingPipeline( + mTree2Int, epsilonTree2_, epsilon2Tree2_, epsilon3Tree2_, false, + useMinMaxPair_, cleanTree_, treesNodeCorr_[1], true, true); + } + + tree1 = &(mTree1Int.tree); + tree2 = &(mTree2Int.tree); + + return computeDistance(tree1, tree2, outputMatching); + } + + template + dataType computeDistance( + ftm::FTMTree_MT *tree1, + ftm::FTMTree_MT *tree2, + std::vector> *outputMatching + = nullptr) { + + // compute preorder of both trees (necessary for bottom-up dynamic + // programming) std::vector> predecessors1(tree1->getNumberOfNodes()); std::vector> predecessors2(tree2->getNumberOfNodes()); @@ -248,6 +309,8 @@ namespace ttk { } } + // initialize memoization tables + size_t nn1 = tree1->getNumberOfNodes(); size_t nn2 = tree2->getNumberOfNodes(); size_t const dim1 = 1; @@ -452,12 +515,14 @@ namespace ttk { + memT[child11 + 1 * dim2 + child22 * dim3 + 1 * dim4]); } else { for(auto child1_mb : children1) { - auto topo1_ = children1; + std::vector topo1_; + tree1->getChildren(curr1, topo1_); topo1_.erase( std::remove(topo1_.begin(), topo1_.end(), child1_mb), topo1_.end()); for(auto child2_mb : children2) { - auto topo2_ = children2; + std::vector topo2_; + tree2->getChildren(curr2, topo2_); topo2_.erase( std::remove(topo2_.begin(), topo2_.end(), child2_mb), topo2_.end()); @@ -551,8 +616,472 @@ namespace ttk { dataType res = memT[children1[0] + 1 * dim2 + children2[0] * dim3 + 1 * dim4]; + if(computeMapping_ && outputMatching) { + + outputMatching->clear(); + std::vector matchedNodes(tree1->getNumberOfNodes(), -1); + std::vector matchedCost(tree1->getNumberOfNodes(), -1); + std::vector, std::pair>> + mapping; + std::vector linkedNodes1(tree1->getNumberOfNodes(), -1); + std::vector linkedNodes2(tree2->getNumberOfNodes(), -1); + traceMapping_branch(tree1, tree2, children1[0], 1, children2[0], 1, + predecessors1, predecessors2, depth1, depth2, memT, + mapping); + // dataType cost_mapping = 0; + for(auto m : mapping) { + if(writeOptimalBranchDecomposition_ && m.first.first >= 0 + && m.first.second >= 0) { + tree1->getNode(m.first.first)->setOrigin(m.first.second); + tree1->getNode(m.first.second)->setOrigin(m.first.first); + linkedNodes1[m.first.first] = m.first.second; + linkedNodes1[m.first.second] = m.first.first; + } + if(writeOptimalBranchDecomposition_ && m.second.first >= 0 + && m.second.second >= 0) { + tree2->getNode(m.second.first)->setOrigin(m.second.second); + tree2->getNode(m.second.second)->setOrigin(m.second.first); + linkedNodes2[m.second.first] = m.second.second; + linkedNodes2[m.second.second] = m.second.first; + } + if(m.first.first == -1) + continue; + if(m.first.second == -1) + continue; + if(m.second.first == -1) + continue; + if(m.second.second == -1) + continue; + matchedNodes[m.first.first] = m.second.first; + matchedNodes[m.first.second] = m.second.second; + matchedCost[m.first.first] + = this->baseMetric_ == 0 ? editCost_Wasserstein1( + m.first.first, m.first.second, m.second.first, m.second.second, + tree1, tree2) + : this->baseMetric_ == 1 ? editCost_Wasserstein2( + m.first.first, m.first.second, m.second.first, + m.second.second, tree1, tree2) + : this->baseMetric_ == 2 + ? editCost_Persistence(m.first.first, m.first.second, + m.second.first, + m.second.second, tree1, tree2) + : editCost_Shifting(m.first.first, m.first.second, + m.second.first, m.second.second, + tree1, tree2); + matchedCost[m.first.second] = matchedCost[m.first.first]; + } + for(ftm::idNode i = 0; i < matchedNodes.size(); i++) { + if(matchedNodes[i] >= 0) + outputMatching->emplace_back( + std::make_tuple(i, matchedNodes[i], matchedCost[i])); + } + } + return squared_ ? std::sqrt(res) : res; } + + template + void traceMapping_branch( + ftm::FTMTree_MT *tree1, + ftm::FTMTree_MT *tree2, + int curr1, + int l1, + int curr2, + int l2, + std::vector> &predecessors1, + std::vector> &predecessors2, + int depth1, + int depth2, + std::vector &memT, + std::vector, std::pair>> + &mapping) { + + int nn1 = tree1->getNumberOfNodes(); + int nn2 = tree2->getNumberOfNodes(); + int dim1 = 1; + int dim2 = (nn1 + 1) * dim1; + int dim3 = (depth1 + 1) * dim2; + int dim4 = (nn2 + 1) * dim3; + + //=============================================================================== + // If second tree empty, track optimal branch decomposition of first tree + + if(curr2 == nn2) { + std::vector children1; + tree1->getChildren(curr1, children1); + int parent1 = predecessors1[curr1][predecessors1[curr1].size() - l1]; + //----------------------------------------------------------------------- + // If first subtree has only one branch, return deletion cost of this + // branch + if(tree1->getNumberOfChildren(curr1) == 0) { + mapping.emplace_back(std::make_pair( + std::make_pair(curr1, parent1), std::make_pair(-1, -1))); + return; + } + //----------------------------------------------------------------------- + // If first subtree has more than one branch, try all decompositions + else { + for(auto child1_mb : children1) { + dataType c_ + = memT[child1_mb + (l1 + 1) * dim2 + nn2 * dim3 + 0 * dim4]; + for(auto child1 : children1) { + if(child1 == child1_mb) { + continue; + } + c_ += memT[child1 + 1 * dim2 + nn2 * dim3 + 0 * dim4]; + } + if(c_ == memT[curr1 + l1 * dim2 + nn2 * dim3 + 0 * dim4]) { + traceMapping_branch(tree1, tree2, child1_mb, (l1 + 1), nn2, 0, + predecessors1, predecessors2, depth1, depth2, + memT, mapping); + for(auto child1 : children1) { + if(child1 == child1_mb) { + continue; + } + traceMapping_branch(tree1, tree2, child1, 1, nn2, 0, + predecessors1, predecessors2, depth1, + depth2, memT, mapping); + } + return; + } + } + this->printErr("Mapping traceback not correct."); + } + } + + //=============================================================================== + // If first tree empty, track optimal branch decomposition of second tree + + if(curr1 == nn1) { + std::vector children2; + tree2->getChildren(curr2, children2); + int parent2 = predecessors2[curr2][predecessors2[curr2].size() - l2]; + //----------------------------------------------------------------------- + // If first subtree has only one branch, return deletion cost of this + // branch + if(tree2->getNumberOfChildren(curr2) == 0) { + mapping.emplace_back(std::make_pair( + std::make_pair(-1, -1), std::make_pair(curr2, parent2))); + return; + } + //----------------------------------------------------------------------- + // If first subtree has more than one branch, try all decompositions + else { + for(auto child2_mb : children2) { + dataType c_ + = memT[nn1 + 0 * dim2 + child2_mb * dim3 + (l2 + 1) * dim4]; + for(auto child2 : children2) { + if(child2 == child2_mb) { + continue; + } + c_ += memT[nn1 + 0 * dim2 + child2 * dim3 + 1 * dim4]; + } + if(c_ == memT[nn1 + 0 * dim2 + curr2 * dim3 + l2 * dim4]) { + traceMapping_branch(tree1, tree2, nn1, 0, child2_mb, (l2 + 1), + predecessors1, predecessors2, depth1, depth2, + memT, mapping); + for(auto child2 : children2) { + if(child2 == child2_mb) { + continue; + } + traceMapping_branch(tree1, tree2, nn1, 0, child2, 1, + predecessors1, predecessors2, depth1, + depth2, memT, mapping); + } + return; + } + } + this->printErr("Mapping traceback not correct."); + } + } + + std::vector children1; + tree1->getChildren(curr1, children1); + std::vector children2; + tree2->getChildren(curr2, children2); + int parent1 = predecessors1[curr1][predecessors1[curr1].size() - l1]; + int parent2 = predecessors2[curr2][predecessors2[curr2].size() - l2]; + + //=============================================================================== + // If both trees not empty, find optimal edit operation + + //--------------------------------------------------------------------------- + // If both trees only have one branch, return edit cost between + // the two branches + if(tree1->getNumberOfChildren(curr1) == 0 + and tree2->getNumberOfChildren(curr2) == 0) { + mapping.emplace_back(std::make_pair( + std::make_pair(curr1, parent1), std::make_pair(curr2, parent2))); + return; + } + //--------------------------------------------------------------------------- + // If first tree only has one branch, try all decompositions of + // second tree + else if(children1.size() == 0) { + for(auto child2_mb : children2) { + dataType d_ + = memT[curr1 + l1 * dim2 + child2_mb * dim3 + (l2 + 1) * dim4]; + for(auto child2 : children2) { + if(child2 == child2_mb) { + continue; + } + d_ += memT[nn1 + 0 * dim2 + child2 * dim3 + 1 * dim4]; + } + if(d_ == memT[curr1 + l1 * dim2 + curr2 * dim3 + l2 * dim4]) { + traceMapping_branch(tree1, tree2, curr1, l1, child2_mb, (l2 + 1), + predecessors1, predecessors2, depth1, depth2, + memT, mapping); + for(auto child2 : children2) { + if(child2 == child2_mb) { + continue; + } + traceMapping_branch(tree1, tree2, nn1, 0, child2, 1, + predecessors1, predecessors2, depth1, depth2, + memT, mapping); + } + return; + } + } + } + //--------------------------------------------------------------------------- + // If second tree only has one branch, try all decompositions of + // first tree + else if(children2.size() == 0) { + for(auto child1_mb : children1) { + dataType d_ + = memT[child1_mb + (l1 + 1) * dim2 + curr2 * dim3 + l2 * dim4]; + for(auto child1 : children1) { + if(child1 == child1_mb) { + continue; + } + d_ += memT[child1 + 1 * dim2 + nn2 * dim3 + 0 * dim4]; + } + if(d_ == memT[curr1 + l1 * dim2 + curr2 * dim3 + l2 * dim4]) { + traceMapping_branch(tree1, tree2, child1_mb, (l1 + 1), curr2, l2, + predecessors1, predecessors2, depth1, depth2, + memT, mapping); + for(auto child1 : children1) { + if(child1 == child1_mb) { + continue; + } + traceMapping_branch(tree1, tree2, child1, 1, nn2, 0, + predecessors1, predecessors2, depth1, depth2, + memT, mapping); + } + return; + } + } + } + //--------------------------------------------------------------------------- + // If both trees have more than one branch, try all decompositions + // of both trees + else { + //----------------------------------------------------------------------- + // Try all possible main branches of first tree (child1_mb) and + // all possible main branches of second tree (child2_mb) Then + // try all possible matchings of subtrees + if(children1.size() == 2 && children2.size() == 2) { + int child11 = children1[0]; + int child12 = children1[1]; + int child21 = children2[0]; + int child22 = children2[1]; + if(memT[curr1 + l1 * dim2 + curr2 * dim3 + l2 * dim4] + == memT[child11 + (l1 + 1) * dim2 + child21 * dim3 + + (l2 + 1) * dim4] + + memT[child12 + 1 * dim2 + child22 * dim3 + 1 * dim4]) { + + traceMapping_branch(tree1, tree2, child11, (l1 + 1), child21, + (l2 + 1), predecessors1, predecessors2, depth1, + depth2, memT, mapping); + traceMapping_branch(tree1, tree2, child12, 1, child22, 1, + predecessors1, predecessors2, depth1, depth2, + memT, mapping); + + return; + } + if(memT[curr1 + l1 * dim2 + curr2 * dim3 + l2 * dim4] + == memT[child12 + (l1 + 1) * dim2 + child22 * dim3 + + (l2 + 1) * dim4] + + memT[child11 + 1 * dim2 + child21 * dim3 + 1 * dim4]) { + + traceMapping_branch(tree1, tree2, child12, (l1 + 1), child22, + (l2 + 1), predecessors1, predecessors2, depth1, + depth2, memT, mapping); + traceMapping_branch(tree1, tree2, child11, 1, child21, 1, + predecessors1, predecessors2, depth1, depth2, + memT, mapping); + + return; + } + if(memT[curr1 + l1 * dim2 + curr2 * dim3 + l2 * dim4] + == memT[child11 + (l1 + 1) * dim2 + child22 * dim3 + + (l2 + 1) * dim4] + + memT[child12 + 1 * dim2 + child21 * dim3 + 1 * dim4]) { + + traceMapping_branch(tree1, tree2, child11, (l1 + 1), child22, + (l2 + 1), predecessors1, predecessors2, depth1, + depth2, memT, mapping); + traceMapping_branch(tree1, tree2, child12, 1, child21, 1, + predecessors1, predecessors2, depth1, depth2, + memT, mapping); + + return; + } + if(memT[curr1 + l1 * dim2 + curr2 * dim3 + l2 * dim4] + == memT[child12 + (l1 + 1) * dim2 + child21 * dim3 + + (l2 + 1) * dim4] + + memT[child11 + 1 * dim2 + child22 * dim3 + 1 * dim4]) { + + traceMapping_branch(tree1, tree2, child12, (l1 + 1), child21, + (l2 + 1), predecessors1, predecessors2, depth1, + depth2, memT, mapping); + traceMapping_branch(tree1, tree2, child11, 1, child22, 1, + predecessors1, predecessors2, depth1, depth2, + memT, mapping); + + return; + } + } else { + for(auto child1_mb : children1) { + std::vector topo1_; + tree1->getChildren(curr1, topo1_); + topo1_.erase(std::remove(topo1_.begin(), topo1_.end(), child1_mb), + topo1_.end()); + for(auto child2_mb : children2) { + std::vector topo2_; + tree2->getChildren(curr2, topo2_); + topo2_.erase(std::remove(topo2_.begin(), topo2_.end(), child2_mb), + topo2_.end()); + + auto f = [&](unsigned r, unsigned c) { + int c1 = r < topo1_.size() ? topo1_[r] : -1; + int c2 = c < topo2_.size() ? topo2_[c] : -1; + return memT[c1 + 1 * dim2 + c2 * dim3 + 1 * dim4]; + }; + int size = std::max(topo1_.size(), topo2_.size()) + 1; + auto costMatrix = std::vector>( + size, std::vector(size, 0)); + std::vector matching; + for(int r = 0; r < size; r++) { + for(int c = 0; c < size; c++) { + costMatrix[r][c] = f(r, c); + } + } + + AssignmentSolver *assignmentSolver; + AssignmentExhaustive solverExhaustive; + AssignmentMunkres solverMunkres; + AssignmentAuction solverAuction; + switch(assignmentSolverID_) { + case 1: + solverExhaustive = AssignmentExhaustive(); + assignmentSolver = &solverExhaustive; + break; + case 2: + solverMunkres = AssignmentMunkres(); + assignmentSolver = &solverMunkres; + break; + case 0: + default: + solverAuction = AssignmentAuction(); + assignmentSolver = &solverAuction; + } + assignmentSolver->setInput(costMatrix); + assignmentSolver->setBalanced(true); + assignmentSolver->run(matching); + dataType d_ = memT[child1_mb + (l1 + 1) * dim2 + child2_mb * dim3 + + (l2 + 1) * dim4]; + for(auto m : matching) + d_ += std::get<2>(m); + + if(d_ == memT[curr1 + l1 * dim2 + curr2 * dim3 + l2 * dim4]) { + traceMapping_branch( + tree1, tree2, child1_mb, (l1 + 1), child2_mb, (l2 + 1), + predecessors1, predecessors2, depth1, depth2, memT, mapping); + for(auto m : matching) { + int n1 = std::get<0>(m) < static_cast(topo1_.size()) + ? topo1_[std::get<0>(m)] + : -1; + int n2 = std::get<1>(m) < static_cast(topo2_.size()) + ? topo2_[std::get<1>(m)] + : -1; + if(n1 >= 0 && n2 >= 0) + traceMapping_branch(tree1, tree2, n1, 1, n2, 1, + predecessors1, predecessors2, depth1, + depth2, memT, mapping); + else if(n1 >= 0) + traceMapping_branch(tree1, tree2, n1, 1, nn2, 0, + predecessors1, predecessors2, depth1, + depth2, memT, mapping); + else if(n2 >= 0) + traceMapping_branch(tree1, tree2, nn1, 0, n2, 1, + predecessors1, predecessors2, depth1, + depth2, memT, mapping); + } + return; + } + } + } + } + //----------------------------------------------------------------------- + // Try to continue main branch on one child of first tree and + // delete all other subtrees Then match continued branch to + // current branch in second tree + for(auto child1_mb : children1) { + dataType d_ + = memT[child1_mb + (l1 + 1) * dim2 + curr2 * dim3 + l2 * dim4]; + for(auto child1 : children1) { + if(child1 == child1_mb) { + continue; + } + d_ += memT[child1 + 1 * dim2 + nn2 * dim3 + 0 * dim4]; + } + if(memT[curr1 + l1 * dim2 + curr2 * dim3 + l2 * dim4] == d_) { + traceMapping_branch(tree1, tree2, child1_mb, (l1 + 1), curr2, l2, + predecessors1, predecessors2, depth1, depth2, + memT, mapping); + for(auto child1 : children1) { + if(child1 == child1_mb) { + continue; + } + traceMapping_branch(tree1, tree2, child1, 1, nn2, 0, + predecessors1, predecessors2, depth1, depth2, + memT, mapping); + } + return; + } + } + //----------------------------------------------------------------------- + // Try to continue main branch on one child of second tree and + // delete all other subtrees Then match continued branch to + // current branch in first tree + for(auto child2_mb : children2) { + dataType d_ + = memT[curr1 + l1 * dim2 + child2_mb * dim3 + (l2 + 1) * dim4]; + for(auto child2 : children2) { + if(child2 == child2_mb) { + continue; + } + d_ += memT[nn1 + 0 * dim2 + child2 * dim3 + 1 * dim4]; + } + if(memT[curr1 + l1 * dim2 + curr2 * dim3 + l2 * dim4] == d_) { + traceMapping_branch(tree1, tree2, curr1, l1, child2_mb, (l2 + 1), + predecessors1, predecessors2, depth1, depth2, + memT, mapping); + for(auto child2 : children2) { + if(child2 == child2_mb) { + continue; + } + traceMapping_branch(tree1, tree2, nn1, 0, child2, 1, + predecessors1, predecessors2, depth1, depth2, + memT, mapping); + } + return; + } + } + this->printErr("Mapping traceback not correct"); + } + } }; } // namespace ttk \ No newline at end of file diff --git a/core/base/mergeTreeClustering/MergeTreeBarycenter.h b/core/base/mergeTreeClustering/MergeTreeBarycenter.h index f74f1f6a28..b226aaff92 100644 --- a/core/base/mergeTreeClustering/MergeTreeBarycenter.h +++ b/core/base/mergeTreeClustering/MergeTreeBarycenter.h @@ -1,6 +1,7 @@ /// \ingroup base /// \class MergeTreeBarycenter /// \author Mathieu Pont (mathieu.pont@lip6.fr) +/// \author Florian Wetzels (wetzels@cs.uni-kl.de) /// \date 2021. /// /// This module defines the %MergeTreeBarycenter class that computes @@ -12,6 +13,12 @@ /// Proc. of IEEE VIS 2021.\n /// IEEE Transactions on Visualization and Computer Graphics, 2021 +/// \b Related \b publication \n +/// "Merge Tree Geodesics and Barycenters with Path Mappings" \n +/// F. Wetzels, M. Pont, J. Tierny and C. Garth.\n +/// Proc. of IEEE VIS 2023.\n +/// IEEE Transactions on Visualization and Computer Graphics, 2024 + #pragma once #include @@ -22,6 +29,10 @@ #include "MergeTreeBase.h" #include "MergeTreeDistance.h" +#include "PathMappingDistance.h" + +#include +#include namespace ttk { @@ -49,6 +60,14 @@ namespace ttk { bool preprocess_ = true; bool postprocess_ = true; + int pathMetric_ = 0; + int baseModule_ = 0; + bool useMedianBarycenter_ = false; + bool useFixedInit_ = false; + // bool useEarlyOut_ = true; + int fixedInitNumber_ = 0; + int iterationLimit_ = 100; + // Output std::vector finalDistances_; @@ -119,6 +138,34 @@ namespace ttk { return finalDistances_; } + void setBaseModule(int m) { + baseModule_ = m; + } + + void setPathMetric(int m) { + pathMetric_ = m; + } + + void setUseMedianBarycenter(bool useMedian) { + useMedianBarycenter_ = useMedian; + } + + void setUseFixedInit(bool useFixedInit) { + useFixedInit_ = useFixedInit; + } + + // void setUseEarlyOut(bool useEarlyOut) { + // useEarlyOut_ = useEarlyOut; + // } + + void setFixedInitNumber(int fixedInitNumber) { + fixedInitNumber_ = fixedInitNumber; + } + + void setIterationLimit(int l) { + iterationLimit_ = l; + } + /** * Implementation of the algorithm. */ @@ -140,9 +187,13 @@ namespace ttk { for(unsigned int i = 0; i < trees.size(); ++i) for(unsigned int j = i + 1; j < trees.size(); ++j) { std::vector> matching; + std::vector, + std::pair>> + matching_path; dataType distance; - computeOneDistance(trees[i], trees2[j], matching, distance, - useDoubleInput, isFirstInput); + computeOneDistance(trees[i], trees2[j], matching, + matching_path, distance, useDoubleInput, + isFirstInput); distanceMatrix[i][j] = distance; distanceMatrix[j][i] = distance; } @@ -273,9 +324,19 @@ namespace ttk { void initBarycenterTree(std::vector &trees, ftm::MergeTree &baryTree, bool distMinimizer = true) { - int const bestIndex - = getBestInitTreeIndex(trees, distMinimizer); - baryTree = ftm::copyMergeTree(trees[bestIndex], true); + int bestIndex; + if(useFixedInit_) { + if(fixedInitNumber_ >= 0 && fixedInitNumber_ < (int)trees.size()) + bestIndex = fixedInitNumber_; + else + bestIndex = 0; + } else + bestIndex = getBestInitTreeIndex(trees, distMinimizer); + // bestIndex = 10; + // baryTree = ftm::copyMergeTree(trees[bestIndex], true); + baryTree + = ftm::copyMergeTree(trees[bestIndex], baseModule_ != 2); + // ftm::FTMTree_MT* bt = &(baryTree.tree); limitSizeBarycenter(baryTree, trees); } @@ -687,6 +748,294 @@ namespace ttk { ftm::cleanMergeTree(baryMergeTree); } + template + void updateBarycenterTree_path( + std::vector &trees, + ftm::MergeTree &baryMergeTree, + std::vector &alphas, + std::vector, + std::pair>>> + &matchings) { + ftm::FTMTree_MT *baryTree = &(baryMergeTree.tree); + double alphaSum = 0; + for(unsigned int i = 0; i < trees.size(); ++i) + alphaSum += alphas[i]; + bool joinTrees = trees[0]->isJoinTree(); + int oldSize = baryTree->getNumberOfNodes(); + + // compute matched and unmatched nodes for all trees and barycenter + std::vector baryNodesMatched(baryTree->getNumberOfNodes(), false); + std::vector> treeNodesMatched(trees.size()); + for(unsigned int i = 0; i < trees.size(); i++) { + if(alphas[i] == 0) + continue; + treeNodesMatched[i].resize(trees[i]->getNumberOfNodes(), false); + for(auto match : matchings[i]) { + baryNodesMatched[match.first.first] = true; + baryNodesMatched[match.first.second] = true; + treeNodesMatched[i][match.second.first] = true; + treeNodesMatched[i][match.second.second] = true; + } + } + // compute size of new barycenter tree + int newSize = oldSize; + for(unsigned int i = 0; i < treeNodesMatched.size(); i++) { + if(alphas[i] == 0 || useMedianBarycenter_) + continue; + for(unsigned int j = 0; j < treeNodesMatched[i].size(); j++) { + if(!treeNodesMatched[i][j]) + newSize++; + } + } + + // Create new barycenter tree + ftm::MergeTree baryMergeTreeNew + = ftm::createEmptyMergeTree(newSize); + // newScalars.resize(newSize); + // ftm::setTreeScalars(baryMergeTreeNew, newScalars); + ftm::FTMTree_MT *baryTreeNew = &(baryMergeTreeNew.tree); + + // Copy the old tree structure + baryTreeNew->copyMergeTreeStructure(baryTree); + + // delete not-matched nodes in barycenter + for(ftm::idNode i = 0; i < baryTree->getNumberOfNodes(); i++) { + if(not baryNodesMatched[i]) { + baryTreeNew->getNode(i)->setOrigin(-1); + baryTreeNew->deleteNode(i); + } + } + + // relabel paths + std::vector> parentEdgeLengths( + baryTree->getNumberOfNodes()); + for(unsigned int i = 0; i < trees.size(); i++) { + if(alphas[i] == 0) + continue; + auto tree = trees[i]; + for(auto match : matchings[i]) { + dataType bv1 = baryTree->getValue(match.first.first); + dataType bv2 = baryTree->getValue(match.first.second); + dataType tv1 = tree->getValue(match.second.first); + dataType tv2 = tree->getValue(match.second.second); + dataType pathRangeB = bv1 > bv2 ? bv1 - bv2 : bv2 - bv1; + dataType pathRangeT = tv1 > tv2 ? tv1 - tv2 : tv2 - tv1; + ftm::idNode currB = baryTreeNew->getParentSafe(match.first.first); + ftm::idNode lastB = match.first.first; + while(lastB != match.first.second) { + dataType currValueB = baryTree->getValue(currB); + dataType lastValueB = baryTree->getValue(lastB); + dataType relativeValueB = lastValueB > currValueB + ? lastValueB - currValueB + : currValueB - lastValueB; + relativeValueB = relativeValueB / pathRangeB; + if(useMedianBarycenter_) + parentEdgeLengths[lastB].emplace_back(relativeValueB + * pathRangeT); + else + parentEdgeLengths[lastB].emplace_back(relativeValueB * pathRangeT + * alphas[i]); + // continue iteration + lastB = currB; + currB = baryTreeNew->getParentSafe(currB); + } + } + } + std::queue q; + q.push(baryTreeNew->getRoot()); + // std::vector newScalars(baryTree->getNumberOfNodes(),0); + std::vector newScalars(newSize, 0); + newScalars[baryTreeNew->getRoot()] + = baryTree->getValue(baryTree->getRoot()); + while(!q.empty()) { + auto curr = q.front(); + q.pop(); + std::vector children; + baryTreeNew->getChildren(curr, children); + for(auto child : children) { + q.emplace(child); + if(useMedianBarycenter_) { + auto m = parentEdgeLengths[child].begin() + + parentEdgeLengths[child].size() / 2; + std::nth_element(parentEdgeLengths[child].begin(), m, + parentEdgeLengths[child].end()); + auto medianEdgeLength + = parentEdgeLengths[child][parentEdgeLengths[child].size() / 2]; + newScalars[child] + = newScalars[curr] + + (joinTrees ? -medianEdgeLength : medianEdgeLength); + } else { + dataType avgEdgeLength = 0; + for(auto l : parentEdgeLengths[child]) { + avgEdgeLength += l; + } + // avgEdgeLength = + // avgEdgeLength/static_cast(trees.size()); + avgEdgeLength = avgEdgeLength / alphaSum; + newScalars[child] + = newScalars[curr] + (joinTrees ? -avgEdgeLength : avgEdgeLength); + } + } + } + setTreeScalars(baryMergeTreeNew, newScalars); + + // insert new nodes + int currSize = oldSize; + for(unsigned int i = 0; i < trees.size(); i++) { + if(alphas[i] == 0 || useMedianBarycenter_) + continue; + auto tree = trees[i]; + std::vector newIndices(tree->getNumberOfNodes(), -1); + for(auto match : matchings[i]) { + dataType bv1 = baryTreeNew->getValue(match.first.first); + dataType bv2 = baryTreeNew->getValue(match.first.second); + dataType tv1 = tree->getValue(match.second.first); + dataType tv2 = tree->getValue(match.second.second); + dataType pathRangeB = bv1 > bv2 ? bv1 - bv2 : bv2 - bv1; + dataType pathRangeT = tv1 > tv2 ? tv1 - tv2 : tv2 - tv1; + ftm::idNode currB = baryTreeNew->getParentSafe(match.first.first); + ftm::idNode currT = tree->getParentSafe(match.second.first); + ftm::idNode lastB = match.first.first; + ftm::idNode lastT = match.second.first; + ftm::idNode lastNode = lastB; + while(currB != match.first.second || currT != match.second.second) { + dataType currValueB = baryTreeNew->getValue(currB); + dataType currValueT = tree->getValue(currT); + dataType relativeValueB + = bv1 > bv2 ? bv1 - currValueB : currValueB - bv1; + dataType relativeValueT + = tv1 > tv2 ? tv1 - currValueT : currValueT - tv1; + relativeValueB = relativeValueB / pathRangeB; + relativeValueT = relativeValueT / pathRangeT; + // if next node in barycenter, ignore + if(relativeValueB < relativeValueT) { + // continue iteration + lastB = currB; + currB = baryTreeNew->getParentSafe(currB); + lastNode = lastB; + } + // if next node in tree, add nodes + else if(relativeValueB > relativeValueT) { + q = std::queue(); + std::vector currChildren; + tree->getChildren(currT, currChildren); + newIndices[currT] = currSize; // newScalars.size(); + currSize++; + ftm::idNode nI = newIndices[currT]; + // newScalars.emplace_back(tree->getValue(currT)); + // newScalars.emplace_back(bv1 + (joinTrees ? relativeValueT * + // pathRangeB : - relativeValueT * pathRangeB)); + newScalars[nI] = bv1 + + (joinTrees ? relativeValueT * pathRangeB + : -relativeValueT * pathRangeB); + baryTreeNew->makeNode(nI); + baryTreeNew->setParent(nI, currB); + baryTreeNew->deleteParent(lastNode); + baryTreeNew->setParent(lastNode, nI); + baryTreeNew->getNode(nI)->setOrigin(-1); + std::vector nodesWithoutLink; + // baryTreeNew->getNode(nI)->setOrigin(newIndices[tree->getNode(currT)->getOrigin()]); + lastNode = newIndices[currT]; + for(auto child : currChildren) { + if(child == lastT) + continue; + q.emplace(child); + newIndices[child] = currSize; // newScalars.size(); + currSize++; + nI = newIndices[child]; + // newScalars.emplace_back(tree->getValue(child)); + dataType edgeLength + = (joinTrees ? tree->getValue(currT) + - tree->getValue(child) + : tree->getValue(child) + - tree->getValue(currT)); + // newScalars.emplace_back(newScalars[newIndices[currT]] + + // (joinTrees ? - edgeLength * (alphas[i]/alphaSum) : edgeLength + // * (alphas[i]/alphaSum))); + newScalars[nI] + = newScalars[newIndices[currT]] + + (joinTrees ? -edgeLength * (alphas[i] / alphaSum) + : edgeLength * (alphas[i] / alphaSum)); + baryTreeNew->makeNode(nI); + baryTreeNew->setParent(nI, newIndices[currT]); + baryTreeNew->getNode(nI)->setOrigin(-1); + if(tree->getNumberOfChildren(child) == 0 + && newIndices[tree->getNode(child)->getOrigin()] >= 0) { + ftm::idNode ln + = newIndices[tree->getNode(child)->getOrigin()]; + baryTreeNew->getNode(nI)->setOrigin(ln); + baryTreeNew->getNode(ln)->setOrigin(nI); + } else { + nodesWithoutLink.push_back(nI); + } + } + while(!q.empty()) { + auto currNode = q.front(); + q.pop(); + currChildren.clear(); + tree->getChildren(currNode, currChildren); + for(auto child : currChildren) { + q.emplace(child); + newIndices[child] = currSize; // newScalars.size(); + currSize++; + nI = newIndices[child]; + // newScalars.emplace_back(tree->getValue(child)); + dataType edgeLength + = (joinTrees ? tree->getValue(currNode) + - tree->getValue(child) + : tree->getValue(child) + - tree->getValue(currNode)); + // newScalars.emplace_back(newScalars[newIndices[currNode]] + + // (joinTrees ? - edgeLength * (alphas[i]/alphaSum) : + // edgeLength * (alphas[i]/alphaSum))); + newScalars[nI] + = newScalars[newIndices[currNode]] + + (joinTrees ? -edgeLength * (alphas[i] / alphaSum) + : edgeLength * (alphas[i] / alphaSum)); + baryTreeNew->makeNode(nI); + baryTreeNew->getNode(nI)->setOrigin(-1); + baryTreeNew->setParent(nI, newIndices[currNode]); + if(tree->getNumberOfChildren(child) == 0 + && newIndices[tree->getNode(child)->getOrigin()] >= 0) { + ftm::idNode ln + = newIndices[tree->getNode(child)->getOrigin()]; + baryTreeNew->getNode(nI)->setOrigin(ln); + baryTreeNew->getNode(ln)->setOrigin(nI); + } else { + nodesWithoutLink.push_back(nI); + } + } + } + // std::cout << + // baryTreeNew->getNode(newIndices[currT])->getOrigin() << " " << + // nodesWithoutLink.size() << std::endl; + if(baryTreeNew->getNode(newIndices[currT])->getOrigin() < 0) { + baryTreeNew->getNode(newIndices[currT]) + ->setOrigin(nodesWithoutLink[0]); + } + for(ftm::idNode n : nodesWithoutLink) { + baryTreeNew->getNode(n)->setOrigin(newIndices[currT]); + } + // continue iteration + lastT = currT; + currT = tree->getParentSafe(currT); + } else { + // this should not happen + printErr("Impossible Matching behaviour."); + lastB = currB; + lastT = currT; + currB = baryTreeNew->getParentSafe(currB); + currT = tree->getParentSafe(currT); + } + } + } + setTreeScalars(baryMergeTreeNew, newScalars); + } + + ftm::cleanMergeTree(baryMergeTreeNew, true); + baryMergeTree = baryMergeTreeNew; + } + template void updateBarycenterTree( std::vector &trees, @@ -703,37 +1052,53 @@ namespace ttk { // ------------------------------------------------------------------------ // Assignment // ------------------------------------------------------------------------ + template void computeOneDistance( ftm::FTMTree_MT *tree, ftm::FTMTree_MT *baryTree, std::vector> &matching, + std::vector, + std::pair>> + &matching_path, dataType &distance, bool useDoubleInput = false, bool isFirstInput = true) { // Timer t_distance; - MergeTreeDistance mergeTreeDistance; - mergeTreeDistance.setDebugLevel(std::min(debugLevel_, 2)); - mergeTreeDistance.setPreprocess(false); - mergeTreeDistance.setPostprocess(false); - mergeTreeDistance.setBranchDecomposition(true); - mergeTreeDistance.setNormalizedWasserstein(normalizedWasserstein_); - mergeTreeDistance.setKeepSubtree(keepSubtree_); - mergeTreeDistance.setAssignmentSolver(assignmentSolverID_); - mergeTreeDistance.setIsCalled(true); - mergeTreeDistance.setThreadNumber(this->threadNumber_); - mergeTreeDistance.setDistanceSquaredRoot(true); // squared root - mergeTreeDistance.setNodePerTask(nodePerTask_); - if(useDoubleInput) { - double const weight = mixDistancesMinMaxPairWeight(isFirstInput); - mergeTreeDistance.setMinMaxPairWeight(weight); + if(baseModule_ == 2) { + PathMappingDistance pathDistance; + pathDistance.setDebugLevel(std::min(debugLevel_, 2)); + pathDistance.setPreprocess(false); + pathDistance.setAssignmentSolver(assignmentSolverID_); + pathDistance.setThreadNumber(this->threadNumber_); + pathDistance.setDistanceSquaredRoot(false); // squared root + pathDistance.setComputeMapping(true); + distance = pathDistance.computeDistance( + baryTree, tree, &matching, &matching_path); + } else { + MergeTreeDistance mergeTreeDistance; + mergeTreeDistance.setDebugLevel(std::min(debugLevel_, 2)); + mergeTreeDistance.setPreprocess(false); + mergeTreeDistance.setPostprocess(false); + mergeTreeDistance.setBranchDecomposition(true); + mergeTreeDistance.setNormalizedWasserstein(normalizedWasserstein_); + mergeTreeDistance.setKeepSubtree(keepSubtree_); + mergeTreeDistance.setAssignmentSolver(assignmentSolverID_); + mergeTreeDistance.setIsCalled(true); + mergeTreeDistance.setThreadNumber(this->threadNumber_); + mergeTreeDistance.setDistanceSquaredRoot(true); // squared root + mergeTreeDistance.setNodePerTask(nodePerTask_); + if(useDoubleInput) { + double const weight = mixDistancesMinMaxPairWeight(isFirstInput); + mergeTreeDistance.setMinMaxPairWeight(weight); + } + /*if(progressiveBarycenter_){ + mergeTreeDistance.setAuctionNoRounds(1); + mergeTreeDistance.setAuctionEpsilonDiviser(NoIteration-1); + }*/ + distance = mergeTreeDistance.computeDistance( + baryTree, tree, matching); } - /*if(progressiveBarycenter_){ - mergeTreeDistance.setAuctionNoRounds(1); - mergeTreeDistance.setAuctionEpsilonDiviser(NoIteration-1); - }*/ - distance - = mergeTreeDistance.computeDistance(baryTree, tree, matching); std::stringstream ss, ss2; ss << "distance tree : " << distance; printMsg(ss.str(), debug::Priority::VERBOSE); @@ -749,11 +1114,15 @@ namespace ttk { ftm::FTMTree_MT *tree, ftm::MergeTree &baryMergeTree, std::vector> &matching, + std::vector, + std::pair>> + &matching_path, dataType &distance, bool useDoubleInput = false, bool isFirstInput = true) { computeOneDistance(tree, &(baryMergeTree.tree), matching, - distance, useDoubleInput, isFirstInput); + matching_path, distance, useDoubleInput, + isFirstInput); } template @@ -761,29 +1130,82 @@ namespace ttk { ftm::MergeTree &baryMergeTree, ftm::MergeTree &baryMergeTree2, std::vector> &matching, + std::vector, + std::pair>> + &matching_path, dataType &distance, bool useDoubleInput = false, bool isFirstInput = true) { computeOneDistance(&(baryMergeTree.tree), baryMergeTree2, - matching, distance, useDoubleInput, + matching, matching_path, distance, + useDoubleInput, isFirstInput); + } + + template + void computeOneDistance( + ftm::FTMTree_MT *tree, + ftm::FTMTree_MT *baryTree, + std::vector> &matching, + dataType &distance, + bool useDoubleInput = false, + bool isFirstInput = true) { + std::vector, + std::pair>> + matching_path; + computeOneDistance(tree, baryTree, matching, matching_path, + distance, useDoubleInput, isFirstInput); + } + + template + void computeOneDistance( + ftm::FTMTree_MT *tree, + ftm::MergeTree &baryMergeTree, + std::vector> &matching, + dataType &distance, + bool useDoubleInput = false, + bool isFirstInput = true) { + std::vector, + std::pair>> + matching_path; + computeOneDistance(tree, &(baryMergeTree.tree), matching, + matching_path, distance, useDoubleInput, isFirstInput); } + template + void computeOneDistance( + ftm::MergeTree &baryMergeTree, + ftm::MergeTree &baryMergeTree2, + std::vector> &matching, + dataType &distance, + bool useDoubleInput = false, + bool isFirstInput = true) { + std::vector, + std::pair>> + matching_path; + computeOneDistance(&(baryMergeTree.tree), baryMergeTree2, + matching, matching_path, distance, + useDoubleInput, isFirstInput); + } + template void assignment( std::vector &trees, ftm::MergeTree &baryMergeTree, std::vector>> &matchings, + std::vector, + std::pair>>> + &matchings_path, std::vector &distances, bool useDoubleInput = false, bool isFirstInput = true) { if(not isCalled_) - assignmentPara(trees, baryMergeTree, matchings, distances, - useDoubleInput, isFirstInput); + assignmentPara(trees, baryMergeTree, matchings, matchings_path, + distances, useDoubleInput, isFirstInput); else - assignmentTask(trees, baryMergeTree, matchings, distances, - useDoubleInput, isFirstInput); + assignmentTask(trees, baryMergeTree, matchings, matchings_path, + distances, useDoubleInput, isFirstInput); } template @@ -792,6 +1214,9 @@ namespace ttk { ftm::MergeTree &baryMergeTree, std::vector>> &matchings, + std::vector, + std::pair>>> + &matchings_path, std::vector &distances, bool useDoubleInput = false, bool isFirstInput = true) { @@ -801,8 +1226,8 @@ namespace ttk { { #pragma omp single nowait #endif - assignmentTask(trees, baryMergeTree, matchings, distances, - useDoubleInput, isFirstInput); + assignmentTask(trees, baryMergeTree, matchings, matchings_path, + distances, useDoubleInput, isFirstInput); #ifdef TTK_ENABLE_OPENMP4 } // pragma omp parallel #endif @@ -814,17 +1239,20 @@ namespace ttk { ftm::MergeTree &baryMergeTree, std::vector>> &matchings, + std::vector, + std::pair>>> + &matchings_path, std::vector &distances, bool useDoubleInput = false, bool isFirstInput = true) { for(unsigned int i = 0; i < trees.size(); ++i) #ifdef TTK_ENABLE_OPENMP4 #pragma omp task firstprivate(i) UNTIED() \ - shared(baryMergeTree, matchings, distances) + shared(baryMergeTree, matchings, matchings_path, distances) #endif computeOneDistance(trees[i], baryMergeTree, matchings[i], - distances[i], useDoubleInput, - isFirstInput); + matchings_path[i], distances[i], + useDoubleInput, isFirstInput); #ifdef TTK_ENABLE_OPENMP4 #pragma omp taskwait #endif @@ -911,6 +1339,9 @@ namespace ttk { std::vector &alphas, std::vector>> &finalMatchings, + std::vector, + std::pair>>> + &finalMatchings_path, bool finalAsgnDoubleInput = false, bool finalAsgnFirstInput = true) { Timer t_bary; @@ -939,7 +1370,11 @@ namespace ttk { dataType minFrechet = std::numeric_limits::max(); int cptBlocked = 0; int NoIteration = 0; - while(not converged) { + std::stringstream energySequence; + int minBarySize = std::numeric_limits::max(); + int maxBarySize = 0; + while(not converged + && (iterationLimit_ < 0 || NoIteration < iterationLimit_)) { ++NoIteration; printMsg(debug::Separator::L2); @@ -950,9 +1385,13 @@ namespace ttk { // --- Assignment std::vector>> matchings(trees.size()); + std::vector, + std::pair>>> + matchings_path(trees.size()); std::vector distances(trees.size(), -1); Timer t_assignment; - assignment(trees, baryMergeTree, matchings, distances); + assignment( + trees, baryMergeTree, matchings, matchings_path, distances); Timer t_addDeletedNodes; if(progressiveBarycenter_) addScaledDeletedNodesCost( @@ -965,7 +1404,13 @@ namespace ttk { // --- Update Timer t_update; - updateBarycenterTree(trees, baryMergeTree, alphas, matchings); + if(baseModule_ == 2) { + updateBarycenterTree_path( + trees, baryMergeTree, alphas, matchings_path); + } else { + updateBarycenterTree( + trees, baryMergeTree, alphas, matchings); + } auto t_update_time = t_update.getElapsedTime(); baryTree = &(baryMergeTree.tree); printMsg("Update", 1, t_update_time, this->threadNumber_, @@ -973,28 +1418,40 @@ namespace ttk { // --- Check convergence dataType currentFrechetEnergy = 0; - for(unsigned int i = 0; i < trees.size(); ++i) + dataType currentFrechetEnergy2 = 0; + for(unsigned int i = 0; i < trees.size(); ++i) { + currentFrechetEnergy2 += alphas[i] * distances[i]; currentFrechetEnergy += alphas[i] * distances[i] * distances[i]; + } auto frechetDiff = std::abs((double)(frechetEnergy - currentFrechetEnergy)); converged = (frechetDiff <= tol_); converged = converged and (not progressiveBarycenter_ or treesUnscaled); frechetEnergy = currentFrechetEnergy; tol_ = frechetEnergy / 125.0; + energySequence << currentFrechetEnergy << std::endl; - std::stringstream ss4; + std::stringstream ss4, ss5; auto barycenterTime = t_bary.getElapsedTime() - addDeletedNodesTime_; printMsg("Total", 1, barycenterTime, this->threadNumber_, debug::LineMode::NEW, debug::Priority::INFO); - printBaryStats(baryTree); + printBaryStats(baryTree, debug::Priority::INFO); ss4 << "Frechet energy : " << frechetEnergy; + ss5 << "Frechet energy non-squared: " << currentFrechetEnergy2; printMsg(ss4.str()); + printMsg(ss5.str()); + + if((int)baryTree->getNumberOfNodes() > maxBarySize) + maxBarySize = baryTree->getNumberOfNodes(); + if((int)baryTree->getNumberOfNodes() < minBarySize) + minBarySize = baryTree->getNumberOfNodes(); minFrechet = std::min(minFrechet, frechetEnergy); if(not converged and (not progressiveBarycenter_ or treesUnscaled)) { cptBlocked = (minFrechet < frechetEnergy) ? cptBlocked + 1 : 0; converged = (cptBlocked >= 10); } + // if(!useEarlyOut_) converged = false; // --- Persistence scaling if(progressiveBarycenter_) { @@ -1004,28 +1461,54 @@ namespace ttk { } } + // std::ofstream energyFile; + // energyFile.open("/home/wetzels/ttk/energy.txt"); + // energyFile << energySequence.str(); + // energyFile.close(); + // Final processing printMsg(debug::Separator::L2); printMsg("Final assignment"); std::vector distances(trees.size(), -1); - assignment(trees, baryMergeTree, finalMatchings, distances, - finalAsgnDoubleInput, finalAsgnFirstInput); + if(baseModule_ == 2) { + assignment( + trees, baryMergeTree, finalMatchings, finalMatchings_path, distances); + } else { + assignment(trees, baryMergeTree, finalMatchings, + finalMatchings_path, distances, + finalAsgnDoubleInput, finalAsgnFirstInput); + } for(auto dist : distances) finalDistances_.push_back(dist); dataType currentFrechetEnergy = 0; - for(unsigned int i = 0; i < trees.size(); ++i) + dataType currentFrechetEnergy2 = 0; + for(unsigned int i = 0; i < trees.size(); ++i) { + currentFrechetEnergy2 += alphas[i] * distances[i]; currentFrechetEnergy += alphas[i] * distances[i] * distances[i]; + } + auto barycenterTime = t_bary.getElapsedTime() - addDeletedNodesTime_; std::stringstream ss, ss2; ss << "Frechet energy : " << currentFrechetEnergy; + ss2 << "Frechet energy non-squared: " << currentFrechetEnergy2; printMsg(ss.str()); - auto barycenterTime = t_bary.getElapsedTime() - addDeletedNodesTime_; + printMsg(ss2.str()); printMsg("Total", 1, barycenterTime, this->threadNumber_, - debug::LineMode::NEW, debug::Priority::INFO); + debug::LineMode::NEW, debug::Priority::PERFORMANCE); // std::cout << "Bary Distance Time = " << allDistanceTime_ << std::endl; - if(trees.size() == 2 and not isCalled_) + std::stringstream ssIt; + ssIt << "Number of iterations: " << NoIteration; + printMsg(ssIt.str(), debug::Priority::PERFORMANCE); + std::stringstream ssMin; + ssMin << "Min barycenter bize: " << minBarySize; + printMsg(ssMin.str(), debug::Priority::PERFORMANCE); + std::stringstream ssMax; + ssMax << "Max barycenter bize: " << maxBarySize; + printMsg(ssMax.str(), debug::Priority::PERFORMANCE); + + if(trees.size() == 2 and not isCalled_ && baseModule_ != 2) verifyBarycenterTwoTrees( trees, baryMergeTree, finalMatchings, distances); @@ -1043,17 +1526,21 @@ namespace ttk { std::vector &alphas, std::vector>> &finalMatchings, + std::vector, + std::pair>>> + &finalMatchings_path, ftm::MergeTree &baryMergeTree, bool finalAsgnDoubleInput = false, bool finalAsgnFirstInput = true) { // --- Preprocessing if(preprocess_) { treesNodeCorr_.resize(trees.size()); - for(unsigned int i = 0; i < trees.size(); ++i) - preprocessingPipeline(trees[i], epsilonTree2_, - epsilon2Tree2_, epsilon3Tree2_, - branchDecomposition_, useMinMaxPair_, - cleanTree_, treesNodeCorr_[i]); + for(unsigned int i = 0; i < trees.size(); ++i) { + preprocessingPipeline( + trees[i], epsilonTree2_, epsilon2Tree2_, epsilon3Tree2_, + branchDecomposition_, useMinMaxPair_, cleanTree_, treesNodeCorr_[i], + true, baseModule_ == 2); + } printTreesStats(trees); } @@ -1064,7 +1551,16 @@ namespace ttk { // --- Execute computeBarycenter(treesT, baryMergeTree, alphas, finalMatchings, - finalAsgnDoubleInput, finalAsgnFirstInput); + finalMatchings_path, finalAsgnDoubleInput, + finalAsgnFirstInput); + + if(baseModule_ == 2) { + ftm::FTMTree_MT *baryTree = &(baryMergeTree.tree); + for(ftm::idNode node = 0; node < baryTree->getNumberOfNodes(); node++) { + baryTree->getNode(node)->setOrigin(-1); + } + preprocessTree(baryTree, false); + } // --- Postprocessing if(postprocess_) { @@ -1082,6 +1578,49 @@ namespace ttk { } } + template + void execute( + std::vector> &trees, + std::vector &alphas, + std::vector>> + &finalMatchings, + ftm::MergeTree &baryMergeTree, + bool finalAsgnDoubleInput = false, + bool finalAsgnFirstInput = true) { + + std::vector, + std::pair>>> + finalMatchings_path; + execute(trees, alphas, finalMatchings, finalMatchings_path, + baryMergeTree, finalAsgnDoubleInput, + finalAsgnFirstInput); + } + + template + void execute( + std::vector> &trees, + std::vector>> + &finalMatchings, + std::vector, + std::pair>>> + &finalMatchings_path, + ftm::MergeTree &baryMergeTree, + bool finalAsgnDoubleInput = false, + bool finalAsgnFirstInput = true) { + std::vector alphas; + if(trees.size() != 2) { + for(unsigned int i = 0; i < trees.size(); ++i) + alphas.push_back(1.0 / trees.size()); + } else { + alphas.push_back(alpha_); + alphas.push_back(1 - alpha_); + } + + execute(trees, alphas, finalMatchings, finalMatchings_path, + baryMergeTree, finalAsgnDoubleInput, + finalAsgnFirstInput); + } + template void execute( std::vector> &trees, @@ -1099,8 +1638,13 @@ namespace ttk { alphas.push_back(1 - alpha_); } - execute(trees, alphas, finalMatchings, baryMergeTree, - finalAsgnDoubleInput, finalAsgnFirstInput); + std::vector, + std::pair>>> + finalMatchings_path; + + execute(trees, alphas, finalMatchings, finalMatchings_path, + baryMergeTree, finalAsgnDoubleInput, + finalAsgnFirstInput); } // ------------------------------------------------------------------------ @@ -1204,8 +1748,11 @@ namespace ttk { &finalMatchings, std::vector distances) { std::vector> matching; + std::vector, + std::pair>> + matching_path; dataType distance; - computeOneDistance(trees[0], trees[1], matching, distance); + computeOneDistance(trees[0], trees[1], matching, matching_path, distance); if(distance != (distances[0] + distances[1])) { std::stringstream ss, ss2, ss3, ss4; ss << "distance T1 T2 : " << distance; diff --git a/core/base/mergeTreeClustering/MergeTreeBase.h b/core/base/mergeTreeClustering/MergeTreeBase.h index 64a1b0b49f..d13463307a 100644 --- a/core/base/mergeTreeClustering/MergeTreeBase.h +++ b/core/base/mergeTreeClustering/MergeTreeBase.h @@ -636,7 +636,8 @@ namespace ttk { bool cleanTreeT, double persistenceThreshold, std::vector &nodeCorr, - bool deleteInconsistentNodes = true) { + bool deleteInconsistentNodes = true, + bool removeMergedSaddles = false) { Timer t_proc; ftm::FTMTree_MT *tree = &(mTree.tree); @@ -650,8 +651,18 @@ namespace ttk { std::vector> treeNodeMerged( tree->getNumberOfNodes()); if(not isPersistenceDiagram_ or convertToDiagram_) { - if(epsilonTree != 0) + if(epsilonTree != 0) { mergeSaddle(tree, epsilonTree, treeNodeMerged); + if(removeMergedSaddles) { + for(unsigned int j = 0; j < treeNodeMerged.size(); j++) { + for(auto k : treeNodeMerged[j]) { + auto nodeToDelete = tree->getNode(k)->getOrigin(); + tree->getNode(k)->setOrigin(j); + tree->getNode(nodeToDelete)->setOrigin(-1); + } + } + } + } } // - Compute branch decomposition @@ -701,11 +712,12 @@ namespace ttk { bool useMinMaxPairT, bool cleanTreeT, std::vector &nodeCorr, - bool deleteInconsistentNodes = true) { + bool deleteInconsistentNodes = true, + bool removeMergedSaddles = false) { preprocessingPipeline( mTree, epsilonTree, epsilon2Tree, epsilon3Tree, branchDecompositionT, useMinMaxPairT, cleanTreeT, persistenceThreshold_, nodeCorr, - deleteInconsistentNodes); + deleteInconsistentNodes, removeMergedSaddles); } void reverseNodeCorr(ftm::FTMTree_MT *tree, std::vector &nodeCorr) { @@ -1293,7 +1305,7 @@ namespace ttk { std::stringstream ss; ss << trees.size() << " trees average [node: " << avgNodes << " / " << avgNodesT << ", depth: " << avgDepth << "]"; - printMsg(ss.str()); + printMsg(ss.str(), debug::Priority::PERFORMANCE); } template diff --git a/core/base/mergeTreeClustering/MergeTreeClustering.h b/core/base/mergeTreeClustering/MergeTreeClustering.h index f11e7040d6..e070e810ad 100644 --- a/core/base/mergeTreeClustering/MergeTreeClustering.h +++ b/core/base/mergeTreeClustering/MergeTreeClustering.h @@ -1,6 +1,7 @@ /// \ingroup base /// \class MergeTreeClustering /// \author Mathieu Pont (mathieu.pont@lip6.fr) +/// \author Florian Wetzels (wetzels@cs.uni-kl.de) /// \date 2021. /// /// This module defines the %MergeTreeClustering class that computes @@ -13,6 +14,12 @@ /// Mathieu Pont, Jules Vidal, Julie Delon, Julien Tierny.\n /// Proc. of IEEE VIS 2021.\n /// IEEE Transactions on Visualization and Computer Graphics, 2021 + +/// \b Related \b publication \n +/// "Merge Tree Geodesics and Barycenters with Path Mappings" \n +/// F. Wetzels, M. Pont, J. Tierny and C. Garth.\n +/// Proc. of IEEE VIS 2023.\n +/// IEEE Transactions on Visualization and Computer Graphics, 2024 /// /// \b Online \b examples: \n /// - (trees[bestIndex], true); + = ftm::copyMergeTree(trees[bestIndex], baseModule_ != 2); limitSizeBarycenter(allCentroids[0][i], trees, limitPercent); ftm::cleanMergeTree(allCentroids[0][i]); if(trees2.size() != 0) { allCentroids[1][i] - = ftm::copyMergeTree(trees2[bestIndex], true); + = ftm::copyMergeTree(trees2[bestIndex], baseModule_ != 2); limitSizeBarycenter(allCentroids[1][i], trees2, limitPercent); ftm::cleanMergeTree(allCentroids[1][i]); } @@ -201,7 +208,8 @@ namespace ttk { distancesAndIndexes[i] = std::make_tuple(-bestDistance_[i], i); std::sort(distancesAndIndexes.begin(), distancesAndIndexes.end()); int const bestIndex = std::get<1>(distancesAndIndexes[noNewCentroid]); - centroid = ftm::copyMergeTree(trees[bestIndex], true); + centroid + = ftm::copyMergeTree(trees[bestIndex], baseModule_ != 2); limitSizeBarycenter(centroid, trees); ftm::cleanMergeTree(centroid); } @@ -440,15 +448,26 @@ namespace ttk { std::vector distances(assignedTrees[i].size(), 0); std::vector distances2(assignedTrees[i].size(), 0); treesMatchingVector matching(trees.size()), matching2(trees2.size()); - assignment( - assignedTrees[i], centroids[i], matching, distances, useDoubleInput_); - matchingsC[i] = matching; - if(trees2.size() != 0) { - assignment(assignedTrees2[i], centroids2[i], matching2, - distances2, useDoubleInput_, false); - matchingsC2[i] = matching2; - for(unsigned int j = 0; j < assignedTreesIndex[i].size(); ++j) - distances[j] = mixDistances(distances[j], distances2[j]); + std::vector, + std::pair>>> + matching_path(trees.size()); + if(baseModule_ == 2) { + assignment( + assignedTrees[i], centroids[i], matching, matching_path, distances); + matchingsC[i] = matching; + } else { + assignment(assignedTrees[i], centroids[i], matching, + matching_path, distances, useDoubleInput_); + matchingsC[i] = matching; + if(trees2.size() != 0) { + assignment(assignedTrees2[i], centroids2[i], matching2, + matching_path, distances2, useDoubleInput_, + false); + matchingsC2[i] = matching2; + for(unsigned int j = 0; j < assignedTreesIndex[i].size(); ++j) + distances[j] + = mixDistances(distances[j], distances2[j]); + } } for(unsigned int j = 0; j < assignedTreesIndex[i].size(); ++j) { int const index = assignedTreesIndex[i][j]; @@ -577,8 +596,8 @@ namespace ttk { for(unsigned int t = 0; t < trees.size(); ++t) lowerBound_[t][i] = 0; } else if(assignedTrees[i].size() == 1) { - centroids[i] - = ftm::copyMergeTree(assignedTrees[i][0], true); + centroids[i] = ftm::copyMergeTree( + assignedTrees[i][0], baseModule_ != 2); limitSizeBarycenter(centroids[i], assignedTrees[i]); ftm::cleanMergeTree(centroids[i]); } else if(not samePreviousAssignment(i)) { @@ -618,25 +637,52 @@ namespace ttk { std::vector>> &finalMatchings) { MergeTreeBarycenter mergeTreeBary; - mergeTreeBary.setDebugLevel(std::min(debugLevel_, 2)); - mergeTreeBary.setBranchDecomposition(true); - mergeTreeBary.setNormalizedWasserstein(normalizedWasserstein_); - mergeTreeBary.setKeepSubtree(keepSubtree_); + + mergeTreeBary.setDebugLevel(std::min(debugLevel_, 1)); + mergeTreeBary.setBaseModule(this->baseModule_); + // mergeTreeBary.setProgressiveComputation(false); mergeTreeBary.setAssignmentSolver(assignmentSolverID_); mergeTreeBary.setIsCalled(true); mergeTreeBary.setThreadNumber(this->threadNumber_); mergeTreeBary.setDistanceSquaredRoot(true); // squared root - mergeTreeBary.setProgressiveBarycenter(progressiveBarycenter_); mergeTreeBary.setDeterministic(deterministic_); mergeTreeBary.setTol(tol_); mergeTreeBary.setBarycenterMaximumNumberOfPairs( barycenterMaximumNumberOfPairs_); mergeTreeBary.setBarycenterSizeLimitPercent(barycenterSizeLimitPercent_); + if(baseModule_ == 2) { + mergeTreeBary.setPathMetric(this->pathMetric_); + mergeTreeBary.setBranchDecomposition(false); + mergeTreeBary.setNormalizedWasserstein(false); + mergeTreeBary.setKeepSubtree(false); + // mergeTreeBary.setUseMinMaxPair(true); + mergeTreeBary.setAddNodes(false); + mergeTreeBary.setPostprocess(false); + } else { + mergeTreeBary.setBranchDecomposition(true); + mergeTreeBary.setNormalizedWasserstein(normalizedWasserstein_); + // mergeTreeBary.setNormalizedWassersteinReg(normalizedWassersteinReg_); + // mergeTreeBary.setRescaledWasserstein(rescaledWasserstein_); + mergeTreeBary.setKeepSubtree(keepSubtree_); + mergeTreeBary.setProgressiveBarycenter(progressiveBarycenter_); + } + + std::vector, + std::pair>>> + finalMatchings_path(trees.size()); mergeTreeBary.computeBarycenter( - trees, baryMergeTree, alphas, finalMatchings); + trees, baryMergeTree, alphas, finalMatchings, finalMatchings_path); addDeletedNodesTime_ += mergeTreeBary.getAddDeletedNodesTime(); + + if(baseModule_ == 2) { + ftm::FTMTree_MT *baryTree = &(baryMergeTree.tree); + for(ftm::idNode node = 0; node < baryTree->getNumberOfNodes(); node++) { + baryTree->getNode(node)->setOrigin(-1); + } + preprocessTree(baryTree, false); + } } // ------------------------------------------------------------------------ @@ -801,7 +847,7 @@ namespace ttk { outputMatching2); // --- Postprocessing - if(postprocess_) { + if(baseModule_ == 0 && postprocess_) { // fixMergedRootOriginClustering(centroids); postprocessingClustering( trees, centroids, outputMatching, clusteringAssignment); @@ -851,9 +897,10 @@ namespace ttk { std::vector> &nodeCorr, bool useMinMaxPairT = true) { for(unsigned int i = 0; i < trees.size(); ++i) { - preprocessingPipeline( - trees[i], epsilonTree2_, epsilon2Tree2_, epsilon3Tree2_, - branchDecomposition_, useMinMaxPairT, cleanTree_, nodeCorr[i]); + preprocessingPipeline(trees[i], epsilonTree2_, epsilon2Tree2_, + epsilon3Tree2_, branchDecomposition_, + useMinMaxPairT, cleanTree_, nodeCorr[i], + true, baseModule_ == 2); if(trees.size() < 40) printTreeStats(trees[i]); } diff --git a/core/base/mergeTreeClustering/MergeTreeDistance.h b/core/base/mergeTreeClustering/MergeTreeDistance.h index 0ce6bb929f..a3fabc7c94 100644 --- a/core/base/mergeTreeClustering/MergeTreeDistance.h +++ b/core/base/mergeTreeClustering/MergeTreeDistance.h @@ -11,6 +11,11 @@ /// Mathieu Pont, Jules Vidal, Julie Delon, Julien Tierny.\n /// Proc. of IEEE VIS 2021.\n /// IEEE Transactions on Visualization and Computer Graphics, 2021 +/// +/// \b Related \b publication \n +/// "Edit Distance between Merge Trees" \n +/// R. Sridharamurthy, T. B. Masood, A. Kamakshidasan and V. Natarajan. \n +/// IEEE Transactions on Visualization and Computer Graphics, 2020. #pragma once diff --git a/core/base/mergeTreeClustering/PathMappingDistance.h b/core/base/mergeTreeClustering/PathMappingDistance.h index 31e5ebeba3..e43cb17afa 100644 --- a/core/base/mergeTreeClustering/PathMappingDistance.h +++ b/core/base/mergeTreeClustering/PathMappingDistance.h @@ -28,6 +28,7 @@ #include // ttk common includes +#include "MergeTreeBase.h" #include #include #include @@ -36,7 +37,7 @@ namespace ttk { - class PathMappingDistance : virtual public Debug { + class PathMappingDistance : virtual public Debug, public MergeTreeBase { private: int baseMetric_ = 0; @@ -44,6 +45,9 @@ namespace ttk { bool squared_ = false; bool computeMapping_ = false; + bool preprocess_ = true; + bool saveTree_ = false; + template inline dataType editCost_Persistence(int n1, int p1, @@ -84,7 +88,7 @@ namespace ttk { std::vector> &predecessors2, int depth1, int depth2, - dataType *memT, + std::vector &memT, std::vector, std::pair>> &mapping) { @@ -184,6 +188,7 @@ namespace ttk { traceMapping_path(tree1, tree2, child12, 1, child22, 1, predecessors1, predecessors2, depth1, depth2, memT, mapping); + return; } if(memT[curr1 + l1 * dim2 + curr2 * dim3 + l2 * dim4] == memT[child11 + 1 * dim2 + child22 * dim3 + 1 * dim4] @@ -331,10 +336,24 @@ namespace ttk { computeMapping_ = m; } + void setPreprocess(bool p) { + preprocess_ = p; + } + + void setSaveTree(bool save) { + saveTree_ = save; + } + template - dataType editDistance_path(ftm::FTMTree_MT *tree1, ftm::FTMTree_MT *tree2) { + dataType computeDistance( + ftm::FTMTree_MT *tree1, + ftm::FTMTree_MT *tree2, + std::vector, + std::pair>> + *outputMatching) { - // initialize memoization tables + // compute preorder of both trees (necessary for bottom-up dynamic + // programming) std::vector> predecessors1(tree1->getNumberOfNodes()); std::vector> predecessors2(tree2->getNumberOfNodes()); @@ -385,6 +404,8 @@ namespace ttk { } } + // initialize memoization tables + size_t nn1 = tree1->getNumberOfNodes(); size_t nn2 = tree2->getNumberOfNodes(); size_t const dim1 = 1; @@ -392,6 +413,8 @@ namespace ttk { size_t const dim3 = (depth1 + 1) * dim2; size_t const dim4 = (nn2 + 1) * dim3; + // std::cout << (nn1 + 1) * (depth1 + 1) * (nn2 + 1) * (depth2 + 1) * + // sizeof(dataType) << std::endl; std::vector memT((nn1 + 1) * (depth1 + 1) * (nn2 + 1) * (depth2 + 1)); @@ -621,7 +644,201 @@ namespace ttk { dataType res = memT[children1[0] + 1 * dim2 + children2[0] * dim3 + 1 * dim4]; + if(computeMapping_ && outputMatching) { + + outputMatching->clear(); + traceMapping_path(tree1, tree2, children1[0], 1, children2[0], 1, + predecessors1, predecessors2, depth1, depth2, memT, + *outputMatching); + } + return squared_ ? std::sqrt(res) : res; } + + template + dataType execute(ftm::MergeTree &mTree1, + ftm::MergeTree &mTree2, + std::vector, + std::pair>> + *outputMatching) { + + ftm::MergeTree mTree1Copy; + ftm::MergeTree mTree2Copy; + if(saveTree_) { + mTree1Copy = ftm::copyMergeTree(mTree1); + mTree2Copy = ftm::copyMergeTree(mTree2); + } + ftm::MergeTree &mTree1Int = (saveTree_ ? mTree1Copy : mTree1); + ftm::MergeTree &mTree2Int = (saveTree_ ? mTree2Copy : mTree2); + ftm::FTMTree_MT *tree1 = &(mTree1Int.tree); + ftm::FTMTree_MT *tree2 = &(mTree2Int.tree); + + // optional preprocessing + if(preprocess_) { + treesNodeCorr_.resize(2); + preprocessingPipeline( + mTree1Int, epsilonTree1_, epsilon2Tree1_, epsilon3Tree1_, + branchDecomposition_, useMinMaxPair_, cleanTree_, treesNodeCorr_[0], + true, true); + preprocessingPipeline( + mTree2Int, epsilonTree2_, epsilon2Tree2_, epsilon3Tree2_, + branchDecomposition_, useMinMaxPair_, cleanTree_, treesNodeCorr_[1], + true, true); + } + + tree1 = &(mTree1Int.tree); + tree2 = &(mTree2Int.tree); + + return computeDistance(tree1, tree2, outputMatching); + } + + template + dataType + computeDistance(ftm::FTMTree_MT *tree1, + ftm::FTMTree_MT *tree2, + std::vector> + *outputMatching) { + + std::vector matchedNodes(tree1->getNumberOfNodes(), -1); + std::vector matchedCost(tree1->getNumberOfNodes(), -1); + std::vector, + std::pair>> + mapping; + dataType res = computeDistance(tree1, tree2, &mapping); + if(computeMapping_ && outputMatching) { + outputMatching->clear(); + for(auto m : mapping) { + matchedNodes[m.first.first] = m.second.first; + matchedNodes[m.first.second] = m.second.second; + matchedCost[m.first.first] = editCost_Persistence( + m.first.first, m.first.second, m.second.first, m.second.second, + tree1, tree2); + if(m.first.second == tree1->getRoot()) { + matchedCost[m.first.second] = matchedCost[m.first.first]; + } + } + for(ftm::idNode i = 0; i < matchedNodes.size(); i++) { + if(matchedNodes[i] >= 0) { + outputMatching->emplace_back( + std::make_tuple(i, matchedNodes[i], matchedCost[i])); + } + } + } + + return res; + } + + template + dataType computeDistance( + ftm::FTMTree_MT *tree1, + ftm::FTMTree_MT *tree2, + std::vector> *outputMatching, + std::vector, + std::pair>> + *outputMatching_path) { + + std::vector matchedNodes(tree1->getNumberOfNodes(), -1); + std::vector matchedCost(tree1->getNumberOfNodes(), -1); + dataType res + = computeDistance(tree1, tree2, outputMatching_path); + if(computeMapping_ && outputMatching) { + outputMatching->clear(); + for(auto m : *outputMatching_path) { + matchedNodes[m.first.first] = m.second.first; + matchedNodes[m.first.second] = m.second.second; + matchedCost[m.first.first] = editCost_Persistence( + m.first.first, m.first.second, m.second.first, m.second.second, + tree1, tree2); + if(m.first.second == tree1->getRoot()) { + matchedCost[m.first.second] = matchedCost[m.first.first]; + } + } + for(ftm::idNode i = 0; i < matchedNodes.size(); i++) { + if(matchedNodes[i] >= 0) { + outputMatching->emplace_back( + std::make_tuple(i, matchedNodes[i], matchedCost[i])); + } + } + } + + return res; + } + + template + dataType execute(ftm::MergeTree &mTree1, + ftm::MergeTree &mTree2, + std::vector> + *outputMatching) { + + ftm::MergeTree mTree1Copy; + ftm::MergeTree mTree2Copy; + if(saveTree_) { + mTree1Copy = ftm::copyMergeTree(mTree1); + mTree2Copy = ftm::copyMergeTree(mTree2); + } + ftm::MergeTree &mTree1Int = (saveTree_ ? mTree1Copy : mTree1); + ftm::MergeTree &mTree2Int = (saveTree_ ? mTree2Copy : mTree2); + ftm::FTMTree_MT *tree1 = &(mTree1Int.tree); + ftm::FTMTree_MT *tree2 = &(mTree2Int.tree); + + // optional preprocessing + if(preprocess_) { + treesNodeCorr_.resize(2); + preprocessingPipeline( + mTree1Int, epsilonTree1_, epsilon2Tree1_, epsilon3Tree1_, false, + useMinMaxPair_, cleanTree_, treesNodeCorr_[0], true, true); + preprocessingPipeline( + mTree2Int, epsilonTree2_, epsilon2Tree2_, epsilon3Tree2_, false, + useMinMaxPair_, cleanTree_, treesNodeCorr_[1], true, true); + } + + tree1 = &(mTree1Int.tree); + tree2 = &(mTree2Int.tree); + + return computeDistance(tree1, tree2, outputMatching); + } + + template + dataType computeDistance(ftm::FTMTree_MT *tree1, ftm::FTMTree_MT *tree2) { + return computeDistance( + tree1, tree2, + (std::vector, + std::pair>> *)nullptr); + } + + template + dataType execute(ftm::MergeTree &mTree1, + ftm::MergeTree &mTree2) { + + ftm::MergeTree mTree1Copy; + ftm::MergeTree mTree2Copy; + if(saveTree_) { + mTree1Copy = ftm::copyMergeTree(mTree1); + mTree2Copy = ftm::copyMergeTree(mTree2); + } + ftm::MergeTree &mTree1Int = (saveTree_ ? mTree1Copy : mTree1); + ftm::MergeTree &mTree2Int = (saveTree_ ? mTree2Copy : mTree2); + ftm::FTMTree_MT *tree1 = &(mTree1Int.tree); + ftm::FTMTree_MT *tree2 = &(mTree2Int.tree); + + // optional preprocessing + if(preprocess_) { + treesNodeCorr_.resize(2); + preprocessingPipeline( + mTree1Int, epsilonTree1_, epsilon2Tree1_, epsilon3Tree1_, false, + useMinMaxPair_, cleanTree_, treesNodeCorr_[0], true, true); + preprocessingPipeline( + mTree2Int, epsilonTree2_, epsilon2Tree2_, epsilon3Tree2_, false, + useMinMaxPair_, cleanTree_, treesNodeCorr_[1], true, true); + } + + tree1 = &(mTree1Int.tree); + tree2 = &(mTree2Int.tree); + + return computeDistance( + tree1, tree2, + (std::vector, + std::pair>> *)nullptr); + } }; } // namespace ttk diff --git a/core/base/mergeTreeDistanceMatrix/MergeTreeDistanceMatrix.h b/core/base/mergeTreeDistanceMatrix/MergeTreeDistanceMatrix.h index 3b57f23a7b..123e951e35 100644 --- a/core/base/mergeTreeDistanceMatrix/MergeTreeDistanceMatrix.h +++ b/core/base/mergeTreeDistanceMatrix/MergeTreeDistanceMatrix.h @@ -1,11 +1,33 @@ /// \ingroup base /// \class ttk::MergeTreeDistanceMatrix /// \author Mathieu Pont +/// \author Florian Wetzels (wetzels@cs.uni-kl.de) /// \date 2021. /// /// This VTK filter uses the ttk::MergeTreeDistanceMatrix module to compute the /// distance matrix of a group of merge trees. /// +/// \b Related \b publication \n +/// "Wasserstein Distances, Geodesics and Barycenters of Merge Trees" \n +/// Mathieu Pont, Jules Vidal, Julie Delon, Julien Tierny.\n +/// Proc. of IEEE VIS 2021.\n +/// IEEE Transactions on Visualization and Computer Graphics, 2021 +/// +/// \b Related \b publication \n +/// "Edit Distance between Merge Trees" \n +/// R. Sridharamurthy, T. B. Masood, A. Kamakshidasan and V. Natarajan. \n +/// IEEE Transactions on Visualization and Computer Graphics, 2020. +/// +/// \b Related \b publication \n +/// "Branch Decomposition-Independent Edit Distances for Merge Trees." \n +/// Florian Wetzels, Heike Leitte, and Christoph Garth. \n +/// Computer Graphics Forum, 2022. +/// +/// \b Related \b publication \n +/// "A Deformation-based Edit Distance for Merge Trees" \n +/// Florian Wetzels, Christoph Garth. \n +/// TopoInVis 2022. +/// /// \b Online \b examples: \n /// - Merge @@ -70,8 +92,22 @@ namespace ttk { void execute(std::vector> &trees, std::vector> &trees2, std::vector> &distanceMatrix) { + treesNodeCorr_.resize(trees.size()); + for(unsigned int i = 0; i < trees.size(); ++i) { + preprocessingPipeline( + trees[i], epsilonTree2_, epsilon2Tree2_, epsilon3Tree2_, + baseModule_ == 0 ? branchDecomposition_ : false, useMinMaxPair_, true, + treesNodeCorr_[i], true, baseModule_ == 2); + } executePara(trees, distanceMatrix); if(trees2.size() != 0) { + std::vector> trees2NodeCorr(trees2.size()); + for(unsigned int i = 0; i < trees.size(); ++i) { + preprocessingPipeline( + trees2[i], epsilonTree2_, epsilon2Tree2_, epsilon3Tree2_, + baseModule_ == 0 ? branchDecomposition_ : false, useMinMaxPair_, + true, treesNodeCorr_[i], true, baseModule_ == 2); + } useDoubleInput_ = true; std::vector> distanceMatrix2( trees2.size(), std::vector(trees2.size())); @@ -80,47 +116,6 @@ namespace ttk { } } - template - void execute(std::vector> &ftmtrees, - std::vector> &distanceMatrix) { - for(unsigned int i = 0; i < distanceMatrix.size(); ++i) { - if(i % std::max(int(distanceMatrix.size() / 10), 1) == 0) { - std::stringstream stream; - stream << i << " / " << distanceMatrix.size(); - printMsg(stream.str()); - } - - BranchMappingDistance branchDist; - branchDist.setBaseMetric(branchMetric_); - branchDist.setAssignmentSolver(assignmentSolverID_); - branchDist.setSquared(not distanceSquaredRoot_); - PathMappingDistance pathDist; - pathDist.setBaseMetric(pathMetric_); - pathDist.setAssignmentSolver(assignmentSolverID_); - pathDist.setSquared(not distanceSquaredRoot_); - pathDist.setComputeMapping(true); - - distanceMatrix[i][i] = 0.0; - // compareTrees(trees[i],&(ftmtrees[i].tree)); - for(unsigned int j = i + 1; j < distanceMatrix[0].size(); ++j) { - // Execute - if(baseModule_ == 0) { - distanceMatrix[i][j] = 0; - } else if(baseModule_ == 1) { - dataType dist = branchDist.editDistance_branch( - &(ftmtrees[i].tree), &(ftmtrees[j].tree)); - distanceMatrix[i][j] = static_cast(dist); - } else if(baseModule_ == 2) { - dataType dist = pathDist.editDistance_path( - &(ftmtrees[i].tree), &(ftmtrees[j].tree)); - distanceMatrix[i][j] = static_cast(dist); - } - // distance matrix is symmetric - distanceMatrix[j][i] = distanceMatrix[i][j]; - } // end for j - } // end for i - } - template void executePara(std::vector> &trees, std::vector> &distanceMatrix, @@ -173,7 +168,9 @@ namespace ttk { mergeTreeDistance.setKeepSubtree(keepSubtree_); mergeTreeDistance.setDistanceSquaredRoot(distanceSquaredRoot_); mergeTreeDistance.setUseMinMaxPair(useMinMaxPair_); - mergeTreeDistance.setSaveTree(true); + mergeTreeDistance.setPreprocess(false); + // mergeTreeDistance.setSaveTree(true); + mergeTreeDistance.setSaveTree(false); mergeTreeDistance.setCleanTree(true); mergeTreeDistance.setIsCalled(true); mergeTreeDistance.setPostprocess(false); @@ -187,8 +184,41 @@ namespace ttk { std::vector> outputMatching; distanceMatrix[i][j] = mergeTreeDistance.execute( trees[i], trees[j], outputMatching); - } else { - distanceMatrix[i][j] = 0; + } else if(baseModule_ == 1) { + BranchMappingDistance branchDist; + branchDist.setBaseMetric(branchMetric_); + branchDist.setAssignmentSolver(assignmentSolverID_); + branchDist.setSquared(distanceSquaredRoot_); + branchDist.setEpsilonTree1(epsilonTree1_); + branchDist.setEpsilonTree2(epsilonTree2_); + branchDist.setEpsilon2Tree1(epsilon2Tree1_); + branchDist.setEpsilon2Tree2(epsilon2Tree2_); + branchDist.setEpsilon3Tree1(epsilon3Tree1_); + branchDist.setEpsilon3Tree2(epsilon3Tree2_); + branchDist.setPersistenceThreshold(persistenceThreshold_); + branchDist.setPreprocess(false); + // branchDist.setSaveTree(true); + branchDist.setSaveTree(false); + dataType dist = branchDist.execute(trees[i], trees[j]); + distanceMatrix[i][j] = static_cast(dist); + } else if(baseModule_ == 2) { + PathMappingDistance pathDist; + pathDist.setBaseMetric(pathMetric_); + pathDist.setAssignmentSolver(assignmentSolverID_); + pathDist.setSquared(distanceSquaredRoot_); + pathDist.setComputeMapping(true); + pathDist.setEpsilonTree1(epsilonTree1_); + pathDist.setEpsilonTree2(epsilonTree2_); + pathDist.setEpsilon2Tree1(epsilon2Tree1_); + pathDist.setEpsilon2Tree2(epsilon2Tree2_); + pathDist.setEpsilon3Tree1(epsilon3Tree1_); + pathDist.setEpsilon3Tree2(epsilon3Tree2_); + pathDist.setPersistenceThreshold(persistenceThreshold_); + pathDist.setPreprocess(false); + // pathDist.setSaveTree(true); + pathDist.setSaveTree(false); + dataType dist = pathDist.execute(trees[i], trees[j]); + distanceMatrix[i][j] = static_cast(dist); } // distance matrix is symmetric distanceMatrix[j][i] = distanceMatrix[i][j]; diff --git a/core/base/mergeTreeTemporalReduction/MergeTreeTemporalReduction.h b/core/base/mergeTreeTemporalReduction/MergeTreeTemporalReduction.h index 5d6de761f5..1e60f66c37 100644 --- a/core/base/mergeTreeTemporalReduction/MergeTreeTemporalReduction.h +++ b/core/base/mergeTreeTemporalReduction/MergeTreeTemporalReduction.h @@ -1,6 +1,7 @@ /// \ingroup base /// \class ttk::MergeTreeTemporalReduction /// \author Mathieu Pont (mathieu.pont@lip6.fr) +/// \author Florian Wetzels (wetzels@cs.uni-kl.de) /// \date 2021. /// /// This module defines the %MergeTreeTemporalReduction class that @@ -11,6 +12,12 @@ /// Mathieu Pont, Jules Vidal, Julie Delon, Julien Tierny.\n /// Proc. of IEEE VIS 2021.\n /// IEEE Transactions on Visualization and Computer Graphics, 2021 + +/// \b Related \b publication \n +/// "Merge Tree Geodesics and Barycenters with Path Mappings" \n +/// F. Wetzels, M. Pont, J. Tierny and C. Garth.\n +/// Proc. of IEEE VIS 2023.\n +/// IEEE Transactions on Visualization and Computer Graphics, 2024 /// /// \b Online \b examples: \n /// - #include #include +#include namespace ttk { @@ -37,6 +45,7 @@ namespace ttk { public MergeTreeBase { protected: double removalPercentage_ = 50.; + bool usePathMappings_ = false; bool useL2Distance_ = false; std::vector> fieldL2_; bool useCustomTimeVariable_ = false; @@ -53,6 +62,10 @@ namespace ttk { useL2Distance_ = useL2; } + void setPathMappings(bool usePM) { + usePathMappings_ = usePM; + } + template dataType computeL2Distance(std::vector &img1, std::vector &img2, @@ -91,31 +104,49 @@ namespace ttk { dataType computeDistance(ftm::MergeTree &mTree1, ftm::MergeTree &mTree2, bool emptyTreeDistance = false) { - MergeTreeDistance mergeTreeDistance; - mergeTreeDistance.setAssignmentSolver(assignmentSolverID_); - mergeTreeDistance.setEpsilonTree1(epsilonTree1_); - mergeTreeDistance.setEpsilonTree2(epsilonTree2_); - mergeTreeDistance.setEpsilon2Tree1(epsilon2Tree1_); - mergeTreeDistance.setEpsilon2Tree2(epsilon2Tree2_); - mergeTreeDistance.setEpsilon3Tree1(epsilon3Tree1_); - mergeTreeDistance.setEpsilon3Tree2(epsilon3Tree2_); - mergeTreeDistance.setBranchDecomposition(branchDecomposition_); - mergeTreeDistance.setParallelize(parallelize_); - mergeTreeDistance.setPersistenceThreshold(persistenceThreshold_); - mergeTreeDistance.setNormalizedWasserstein(normalizedWasserstein_); - mergeTreeDistance.setKeepSubtree(keepSubtree_); - mergeTreeDistance.setUseMinMaxPair(useMinMaxPair_); - mergeTreeDistance.setThreadNumber(this->threadNumber_); - mergeTreeDistance.setDistanceSquaredRoot(true); // squared root - mergeTreeDistance.setDebugLevel(2); - mergeTreeDistance.setPreprocess(false); - mergeTreeDistance.setPostprocess(false); - // mergeTreeDistance.setIsCalled(true); - mergeTreeDistance.setOnlyEmptyTreeDistance(emptyTreeDistance); - - std::vector> matching; - dataType distance - = mergeTreeDistance.execute(mTree1, mTree2, matching); + dataType distance; + if(usePathMappings_) { + PathMappingDistance mergeTreeDistance; + mergeTreeDistance.setAssignmentSolver(assignmentSolverID_); + mergeTreeDistance.setEpsilonTree1(epsilonTree1_); + mergeTreeDistance.setEpsilonTree2(epsilonTree2_); + mergeTreeDistance.setPersistenceThreshold(persistenceThreshold_); + mergeTreeDistance.setThreadNumber(this->threadNumber_); + mergeTreeDistance.setDistanceSquaredRoot(false); + mergeTreeDistance.setDebugLevel(2); // ToDo: Why?? + mergeTreeDistance.setPreprocess(false); + mergeTreeDistance.setComputeMapping(false); + + ftm::FTMTree_MT *mt1 = &(mTree1.tree); + ftm::FTMTree_MT *mt2 = &(mTree2.tree); + distance = mergeTreeDistance.computeDistance(mt1, mt2); + } else { + MergeTreeDistance mergeTreeDistance; + mergeTreeDistance.setAssignmentSolver(assignmentSolverID_); + mergeTreeDistance.setEpsilonTree1(epsilonTree1_); + mergeTreeDistance.setEpsilonTree2(epsilonTree2_); + mergeTreeDistance.setEpsilon2Tree1(epsilon2Tree1_); + mergeTreeDistance.setEpsilon2Tree2(epsilon2Tree2_); + mergeTreeDistance.setEpsilon3Tree1(epsilon3Tree1_); + mergeTreeDistance.setEpsilon3Tree2(epsilon3Tree2_); + mergeTreeDistance.setBranchDecomposition(branchDecomposition_); + mergeTreeDistance.setParallelize(parallelize_); + mergeTreeDistance.setPersistenceThreshold(persistenceThreshold_); + mergeTreeDistance.setNormalizedWasserstein(normalizedWasserstein_); + mergeTreeDistance.setKeepSubtree(keepSubtree_); + mergeTreeDistance.setUseMinMaxPair(useMinMaxPair_); + mergeTreeDistance.setThreadNumber(this->threadNumber_); + mergeTreeDistance.setDistanceSquaredRoot(true); // squared root + mergeTreeDistance.setDebugLevel(2); + mergeTreeDistance.setPreprocess(false); + mergeTreeDistance.setPostprocess(false); + // mergeTreeDistance.setIsCalled(true); + mergeTreeDistance.setOnlyEmptyTreeDistance(emptyTreeDistance); + + std::vector> matching; + distance + = mergeTreeDistance.execute(mTree1, mTree2, matching); + } return distance; } @@ -135,7 +166,6 @@ namespace ttk { mergeTreeBarycenter.setBranchDecomposition(branchDecomposition_); mergeTreeBarycenter.setParallelize(parallelize_); mergeTreeBarycenter.setPersistenceThreshold(persistenceThreshold_); - mergeTreeBarycenter.setNormalizedWasserstein(normalizedWasserstein_); mergeTreeBarycenter.setKeepSubtree(keepSubtree_); mergeTreeBarycenter.setUseMinMaxPair(useMinMaxPair_); mergeTreeBarycenter.setThreadNumber(this->threadNumber_); @@ -145,6 +175,21 @@ namespace ttk { mergeTreeBarycenter.setPostprocess(false); // mergeTreeBarycenter.setIsCalled(true); + if(usePathMappings_) { + mergeTreeBarycenter.setBaseModule(2); + mergeTreeBarycenter.setBranchDecomposition(false); + mergeTreeBarycenter.setNormalizedWasserstein(false); + mergeTreeBarycenter.setKeepSubtree(false); + mergeTreeBarycenter.setUseMinMaxPair(false); + mergeTreeBarycenter.setAddNodes(false); + mergeTreeBarycenter.setPostprocess(false); + } else { + mergeTreeBarycenter.setBranchDecomposition(true); + mergeTreeBarycenter.setNormalizedWasserstein(normalizedWasserstein_); + // mergeTreeBarycenter.setNormalizedWassersteinReg(normalizedWassersteinReg_); + // mergeTreeBarycenter.setRescaledWasserstein(rescaledWasserstein_); + } + std::vector> intermediateTrees; intermediateTrees.push_back(mTree1); intermediateTrees.push_back(mTree2); @@ -172,7 +217,7 @@ namespace ttk { std::vector treeRemoved(mTrees.size(), false); int toRemoved = mTrees.size() * removalPercentage_ / 100.; - toRemoved = std::min(toRemoved, (int)(mTrees.size() - 3)); + toRemoved = std::min(toRemoved, (int)(mTrees.size() - 2)); std::vector> images(fieldL2_.size()); for(size_t i = 0; i < fieldL2_.size(); ++i) @@ -312,16 +357,53 @@ namespace ttk { Timer t_tempSub; // --- Preprocessing - if(not useL2Distance_) { + if(not useL2Distance_) { //} && not usePathMappings_) { treesNodeCorr_ = std::vector>(mTrees.size()); for(unsigned int i = 0; i < mTrees.size(); ++i) { - preprocessingPipeline(mTrees[i], epsilonTree2_, - epsilon2Tree2_, epsilon3Tree2_, - branchDecomposition_, useMinMaxPair_, - cleanTree_, treesNodeCorr_[i]); + preprocessingPipeline( + mTrees[i], epsilonTree2_, epsilon2Tree2_, epsilon3Tree2_, + branchDecomposition_, useMinMaxPair_, cleanTree_, treesNodeCorr_[i], + true, usePathMappings_); } printTreesStats(mTrees); } + // if(usePathMappings_){ + // treesNodeCorr_ = std::vector>(mTrees.size()); + // printMsg("uses path mapping distance"); + // for(unsigned int i = 0; i < mTrees.size(); ++i) { + // ftm::FTMTree_MT *tree = &(mTrees[i].tree); + // preprocessTree(tree, true); + + // // - Delete null persistence pairs and persistence thresholding + // persistenceThresholding(tree, persistenceThreshold_); + + // // - Merge saddle points according epsilon + // if(not isPersistenceDiagram_) { + // if(epsilonTree2_ != 0){ + // std::vector> treeNodeMerged( + // tree->getNumberOfNodes() ); mergeSaddle(tree, + // epsilonTree2_, treeNodeMerged); for(unsigned int j=0; + // jgetNode(j)->getOrigin(); + // tree->getNode(k)->setOrigin(j); + // tree->getNode(nodeToDelete)->setOrigin(-1); + // } + // } + // ftm::cleanMergeTree(mTrees[i], treesNodeCorr_[i], + // true); + // } + // else{ + // std::vector + // nodeCorri(tree->getNumberOfNodes()); for(unsigned int j=0; + // j(tree, false); + // } + // } // --- Execute std::vector> barycenters(mTrees.size()); @@ -367,7 +449,7 @@ namespace ttk { } // --- Postprocessing - if(not useL2Distance_) { + if(not useL2Distance_ && not usePathMappings_) { for(unsigned int i = 0; i < allMT.size(); ++i) postprocessingPipeline(&(allMT[i].tree)); for(unsigned int i = 0; i < mTrees.size(); ++i) diff --git a/core/base/mergeTreeTemporalReductionDecoding/MergeTreeTemporalReductionDecoding.h b/core/base/mergeTreeTemporalReductionDecoding/MergeTreeTemporalReductionDecoding.h index eb6ddd02bd..801b2b20a6 100644 --- a/core/base/mergeTreeTemporalReductionDecoding/MergeTreeTemporalReductionDecoding.h +++ b/core/base/mergeTreeTemporalReductionDecoding/MergeTreeTemporalReductionDecoding.h @@ -1,6 +1,7 @@ /// \ingroup base /// \class ttk::MergeTreeTemporalReductionDecoding /// \author Mathieu Pont (mathieu.pont@lip6.fr) +/// \author Florian Wetzels (wetzels@cs.uni-kl.de) /// \date 2021. /// /// This module defines the %MergeTreeTemporalReductionDecoding class that @@ -11,6 +12,12 @@ /// Mathieu Pont, Jules Vidal, Julie Delon, Julien Tierny.\n /// Proc. of IEEE VIS 2021.\n /// IEEE Transactions on Visualization and Computer Graphics, 2021 + +/// \b Related \b publication \n +/// "Merge Tree Geodesics and Barycenters with Path Mappings" \n +/// F. Wetzels, M. Pont, J. Tierny and C. Garth.\n +/// Proc. of IEEE VIS 2023.\n +/// IEEE Transactions on Visualization and Computer Graphics, 2024 /// /// \b Online \b examples: \n /// - #include #include +#include namespace ttk { @@ -36,40 +44,69 @@ namespace ttk { public MergeTreeBase { protected: std::vector finalDistances_, distancesToKeyFrames_; + bool usePathMappings_ = false; public: MergeTreeTemporalReductionDecoding(); + void setPathMappings(bool usePM) { + usePathMappings_ = usePM; + } + template dataType computeDistance( ftm::MergeTree &mTree1, ftm::MergeTree &mTree2, std::vector> &matching) { - MergeTreeDistance mergeTreeDistance; - mergeTreeDistance.setAssignmentSolver(assignmentSolverID_); - mergeTreeDistance.setEpsilonTree1(epsilonTree1_); - mergeTreeDistance.setEpsilonTree2(epsilonTree2_); - mergeTreeDistance.setEpsilon2Tree1(epsilon2Tree1_); - mergeTreeDistance.setEpsilon2Tree2(epsilon2Tree2_); - mergeTreeDistance.setEpsilon3Tree1(epsilon3Tree1_); - mergeTreeDistance.setEpsilon3Tree2(epsilon3Tree2_); - mergeTreeDistance.setBranchDecomposition(branchDecomposition_); - mergeTreeDistance.setParallelize(parallelize_); - mergeTreeDistance.setPersistenceThreshold(persistenceThreshold_); - mergeTreeDistance.setNormalizedWasserstein(normalizedWasserstein_); - mergeTreeDistance.setKeepSubtree(keepSubtree_); - mergeTreeDistance.setUseMinMaxPair(useMinMaxPair_); - mergeTreeDistance.setThreadNumber(this->threadNumber_); - mergeTreeDistance.setDistanceSquaredRoot(true); // squared root - mergeTreeDistance.setDebugLevel(2); - mergeTreeDistance.setPreprocess(false); - mergeTreeDistance.setPostprocess(false); - // mergeTreeDistance.setIsCalled(true); - - dataType distance - = mergeTreeDistance.execute(mTree1, mTree2, matching); - - return distance; + + if(usePathMappings_) { + PathMappingDistance mergeTreeDistance; + mergeTreeDistance.setAssignmentSolver(assignmentSolverID_); + mergeTreeDistance.setEpsilonTree1(epsilonTree1_); + mergeTreeDistance.setEpsilonTree2(epsilonTree2_); + mergeTreeDistance.setPersistenceThreshold(persistenceThreshold_); + mergeTreeDistance.setThreadNumber(this->threadNumber_); + mergeTreeDistance.setDistanceSquaredRoot(false); // squared root + mergeTreeDistance.setDebugLevel(2); + mergeTreeDistance.setPreprocess(false); + mergeTreeDistance.setComputeMapping(true); + + ftm::FTMTree_MT *mt1 = &(mTree1.tree); + ftm::FTMTree_MT *mt2 = &(mTree2.tree); + dataType distance + = mergeTreeDistance.computeDistance(mt1, mt2, &matching); + + return distance; + } else { + MergeTreeDistance mergeTreeDistance; + mergeTreeDistance.setAssignmentSolver(assignmentSolverID_); + mergeTreeDistance.setEpsilonTree1(epsilonTree1_); + mergeTreeDistance.setEpsilonTree2(epsilonTree2_); + mergeTreeDistance.setEpsilon2Tree1(epsilon2Tree1_); + mergeTreeDistance.setEpsilon2Tree2(epsilon2Tree2_); + mergeTreeDistance.setEpsilon3Tree1(epsilon3Tree1_); + mergeTreeDistance.setEpsilon3Tree2(epsilon3Tree2_); + // mergeTreeDistance.setProgressiveComputation(progressiveComputation_); + mergeTreeDistance.setBranchDecomposition(branchDecomposition_); + mergeTreeDistance.setParallelize(parallelize_); + mergeTreeDistance.setPersistenceThreshold(persistenceThreshold_); + mergeTreeDistance.setNormalizedWasserstein(normalizedWasserstein_); + // mergeTreeDistance.setNormalizedWassersteinReg(normalizedWassersteinReg_); + // mergeTreeDistance.setRescaledWasserstein(rescaledWasserstein_); + mergeTreeDistance.setKeepSubtree(keepSubtree_); + mergeTreeDistance.setUseMinMaxPair(useMinMaxPair_); + mergeTreeDistance.setThreadNumber(this->threadNumber_); + mergeTreeDistance.setDistanceSquaredRoot(true); // squared root + mergeTreeDistance.setDebugLevel(2); + mergeTreeDistance.setPreprocess(false); + mergeTreeDistance.setPostprocess(false); + // mergeTreeDistance.setIsCalled(true); + + dataType distance + = mergeTreeDistance.execute(mTree1, mTree2, matching); + + return distance; + } } template @@ -94,7 +131,6 @@ namespace ttk { mergeTreeBarycenter.setBranchDecomposition(branchDecomposition_); mergeTreeBarycenter.setParallelize(parallelize_); mergeTreeBarycenter.setPersistenceThreshold(persistenceThreshold_); - mergeTreeBarycenter.setNormalizedWasserstein(normalizedWasserstein_); mergeTreeBarycenter.setKeepSubtree(keepSubtree_); mergeTreeBarycenter.setUseMinMaxPair(useMinMaxPair_); mergeTreeBarycenter.setThreadNumber(this->threadNumber_); @@ -104,6 +140,21 @@ namespace ttk { mergeTreeBarycenter.setPostprocess(false); // mergeTreeBarycenter.setIsCalled(true); + if(usePathMappings_) { + mergeTreeBarycenter.setBaseModule(2); + mergeTreeBarycenter.setBranchDecomposition(false); + mergeTreeBarycenter.setNormalizedWasserstein(false); + mergeTreeBarycenter.setKeepSubtree(false); + mergeTreeBarycenter.setUseMinMaxPair(false); + mergeTreeBarycenter.setAddNodes(false); + mergeTreeBarycenter.setPostprocess(false); + } else { + mergeTreeBarycenter.setBranchDecomposition(true); + mergeTreeBarycenter.setNormalizedWasserstein(normalizedWasserstein_); + // mergeTreeBarycenter.setNormalizedWassersteinReg(normalizedWassersteinReg_); + // mergeTreeBarycenter.setRescaledWasserstein(rescaledWasserstein_); + } + std::vector> intermediateTrees; intermediateTrees.push_back(mTree1); intermediateTrees.push_back(mTree2); @@ -125,14 +176,63 @@ namespace ttk { Timer t_tempSub; // --- Preprocessing + // if(!usePathMappings_){ + // treesNodeCorr_ = std::vector>(mTrees.size()); + // for(unsigned int i = 0; i < mTrees.size(); ++i) { + // preprocessingPipeline( + // mTrees[i], epsilonTree2_, epsilon2Tree2_, epsilon3Tree2_, + // branchDecomposition_, useMinMaxPair_, cleanTree_, + // treesNodeCorr_[i]); + // } + // printTreesStats(mTrees); + // } treesNodeCorr_ = std::vector>(mTrees.size()); for(unsigned int i = 0; i < mTrees.size(); ++i) { preprocessingPipeline( mTrees[i], epsilonTree2_, epsilon2Tree2_, epsilon3Tree2_, - branchDecomposition_, useMinMaxPair_, cleanTree_, treesNodeCorr_[i]); + branchDecomposition_, useMinMaxPair_, cleanTree_, treesNodeCorr_[i], + true, usePathMappings_); } printTreesStats(mTrees); + // if(usePathMappings_){ + // treesNodeCorr_ = std::vector>(mTrees.size()); + // printMsg("uses path mapping distance"); + // for(unsigned int i = 0; i < mTrees.size(); ++i) { + // ftm::FTMTree_MT *tree = &(mTrees[i].tree); + // preprocessTree(tree, true); + + // // - Delete null persistence pairs and persistence thresholding + // persistenceThresholding(tree, persistenceThreshold_); + + // // - Merge saddle points according epsilon + // if(not isPersistenceDiagram_) { + // if(epsilonTree2_ != 0){ + // std::vector> treeNodeMerged( + // tree->getNumberOfNodes() ); mergeSaddle(tree, + // epsilonTree2_, treeNodeMerged); for(unsigned int j=0; + // jgetNode(j)->getOrigin(); + // tree->getNode(k)->setOrigin(j); + // tree->getNode(nodeToDelete)->setOrigin(-1); + // } + // } + // ftm::cleanMergeTree(mTrees[i], treesNodeCorr_[i], + // true); + // } + // else{ + // std::vector + // nodeCorri(tree->getNumberOfNodes()); for(unsigned int j=0; + // j(tree, false); + // } + // } + // --- Execute distancesToKeyFrames_ = std::vector(coefs.size() * 2); int index = 0; @@ -164,10 +264,12 @@ namespace ttk { = computeDistance(allMT[i], allMT[i + 1], allMatching[i]); // --- Postprocessing - for(unsigned int i = 0; i < allMT.size(); ++i) - postprocessingPipeline(&(allMT[i].tree)); - for(unsigned int i = 0; i < mTrees.size(); ++i) - postprocessingPipeline(&(mTrees[i].tree)); + if(!usePathMappings_) { + for(unsigned int i = 0; i < allMT.size(); ++i) + postprocessingPipeline(&(allMT[i].tree)); + for(unsigned int i = 0; i < mTrees.size(); ++i) + postprocessingPipeline(&(mTrees[i].tree)); + } // --- Print results std::stringstream ss, ss2, ss3; diff --git a/core/base/planarGraphLayout/MergeTreeVisualization.h b/core/base/planarGraphLayout/MergeTreeVisualization.h index f858253516..a7e4e9914f 100644 --- a/core/base/planarGraphLayout/MergeTreeVisualization.h +++ b/core/base/planarGraphLayout/MergeTreeVisualization.h @@ -17,6 +17,7 @@ namespace ttk { protected: // Visualization parameters bool branchDecompositionPlanarLayout_ = false; + bool pathPlanarLayout_ = false; double branchSpacing_ = 1.; bool rescaleTreesIndividually_ = false; double importantPairs_ = 50.; // important pairs threshold @@ -32,13 +33,16 @@ namespace ttk { MergeTreeVisualization() = default; ~MergeTreeVisualization() override = default; - // ========================================================================== + // ======================================================================== // Getter / Setter - // ========================================================================== + // ======================================================================== // Visualization parameters void setBranchDecompositionPlanarLayout(bool b) { branchDecompositionPlanarLayout_ = b; } + void setPathPlanarLayout(bool b) { + pathPlanarLayout_ = b; + } void setBranchSpacing(double d) { branchSpacing_ = d; } @@ -66,9 +70,9 @@ namespace ttk { parseExcludeImportantPairsString(d, excludeImportantPairsLowerValues_); } - // ========================================================================== - // Planar Layout - // ========================================================================== + // ======================================================================== + // Branch Decomposition Tree Planar Layout + // ======================================================================== // TODO manage multi pers pairs template void treePlanarLayoutBDImpl( @@ -262,6 +266,141 @@ namespace ttk { } } + // ======================================================================== + // Path Planar Layout + // ======================================================================== + // TODO manage multi pers pairs + template + void pathPlanarLayout(ftm::FTMTree_MT *tree, + std::vector &retVec, + std::vector &treeSimplexId, + std::vector &leaves, + double importantPairsGap) { + std::queue queue; + for(auto &node : leaves) + queue.emplace(node); + std::vector> bounds(tree->getNumberOfNodes()); + std::vector nodeDone(tree->getNumberOfNodes(), false); + std::vector parentOfImportantPair(tree->getNumberOfNodes(), false); + std::vector childSize(tree->getNumberOfNodes()), + noChildDone(tree->getNumberOfNodes(), 0); + std::vector lowestValue(tree->getNumberOfNodes()); + bool isJT = tree->isJoinTree(); + for(unsigned int i = 0; i < tree->getNumberOfNodes(); ++i) { + std::vector children; + tree->getChildren(i, children); + childSize[i] = children.size(); + } + while(!queue.empty()) { + ftm::idNode node = queue.front(); + queue.pop(); + if(nodeDone[node]) + continue; + + std::vector children; + if(!tree->isLeaf(node) and !tree->isRoot(node)) + tree->getChildren(node, children); + + // Shift children + bool isNodeImportant = tree->isImportantPair( + node, importantPairs_, excludeImportantPairsLowerValues_, + excludeImportantPairsHigherValues_); + if(isNodeImportant) { + bounds[node] + = {retVec[treeSimplexId[node] * 2], retVec[treeSimplexId[node] * 2], + retVec[treeSimplexId[node] * 2 + 1], + retVec[treeSimplexId[node] * 2 + 1]}; + parentOfImportantPair[node] = true; + if(!tree->isLeaf(node) and !tree->isRoot(node)) { + float nodeX = retVec[treeSimplexId[node] * 2]; + ftm::idNode child1 = children[0]; + ftm::idNode child2 = children[1]; + if(not nodeDone[child1] or not nodeDone[child2]) + printErr("not nodeDone[child1] or not nodeDone[child2]"); + if(children.size() != 2) + printWrn("children.size() != 2"); + + double sign = (lowestValue[child1] > lowestValue[child2] ? -1 : 1); + if(isJT) + sign *= -1; + + float child1XBound = bounds[child1][(sign == -1 ? 1 : 0)]; + double child1Shift + = -child1XBound + nodeX + sign * importantPairsGap / 2.0; + shiftSubtreeBounds( + tree, child1, child1Shift, retVec, treeSimplexId); + bounds[child1][0] += child1Shift; + bounds[child1][1] += child1Shift; + float child2XBound = bounds[child2][(sign == -1 ? 0 : 1)]; + double child2Shift + = -child2XBound + nodeX + sign * -1 * importantPairsGap / 2.0; + shiftSubtreeBounds( + tree, child2, child2Shift, retVec, treeSimplexId); + bounds[child2][0] += child2Shift; + bounds[child2][1] += child2Shift; + } + } + + // Update bounds + if(!tree->isLeaf(node) and !tree->isRoot(node)) { + for(auto &child : children) { + bool isChildImportant = tree->isImportantPair( + child, importantPairs_, excludeImportantPairsLowerValues_, + excludeImportantPairsHigherValues_); + if((isChildImportant or parentOfImportantPair[child]) + and not parentOfImportantPair[node]) + bounds[node] = bounds[child]; + if(isChildImportant or parentOfImportantPair[child]) { + bounds[node] = {std::min(bounds[node][0], bounds[child][0]), + std::max(bounds[node][1], bounds[child][1]), + std::min(bounds[node][2], bounds[child][2]), + std::max(bounds[node][3], bounds[child][3])}; + parentOfImportantPair[node] = true; + } + } + } + + // Update lowest value + lowestValue[node] = tree->getValue(node); + for(auto &child : children) { + if(isJT) + lowestValue[node] = std::min(lowestValue[node], lowestValue[child]); + else + lowestValue[node] = std::max(lowestValue[node], lowestValue[child]); + } + + // + nodeDone[node] = true; + ftm::idNode parent = tree->getParentSafe(node); + noChildDone[parent] += 1; + if(noChildDone[parent] == childSize[parent]) + queue.emplace(parent); + } + } + + void shiftSubtreeBounds(ftm::FTMTree_MT *tree, + ftm::idNode subtreeRoot, + double shift, + std::vector &retVec, + std::vector &treeSimplexId) { + std::queue queue; + queue.emplace(subtreeRoot); + while(!queue.empty()) { + ftm::idNode node = queue.front(); + queue.pop(); + + retVec[treeSimplexId[node] * 2] += shift; + + std::vector children; + tree->getChildren(node, children); + for(auto &child : children) + queue.emplace(child); + } + } + + // ======================================================================== + // Merge Tree Planar Layout + // ======================================================================== // TODO manage multi pers pairs template void treePlanarLayoutImpl( @@ -302,6 +441,7 @@ namespace ttk { std::queue queue; ftm::idNode const treeRoot = tree->getRoot(); ftm::idNode const treeRootOrigin = tree->getNode(treeRoot)->getOrigin(); + ftm::idNode const lowestNode = tree->getLowestNode(treeRoot); queue.emplace(treeRoot); while(!queue.empty()) { ftm::idNode const node = queue.front(); @@ -370,8 +510,11 @@ namespace ttk { if(not rescaleTreesIndividually_) { // diff *= getNodePersistence(tree, treeRoot) / // refPersistence; - diff = tree->getNodePersistence(treeRoot); - offset = tree->getBirth(treeRoot); + dataType rootVal = tree->getValue(treeRoot); + dataType lowestNodeVal = tree->getValue(lowestNode); + diff = (rootVal > lowestNodeVal ? rootVal - lowestNodeVal + : lowestNodeVal - rootVal); + offset = std::min(rootVal, lowestNodeVal); } for(int i = 0; i < outNumberOfPoints; i += 2) { @@ -405,44 +548,28 @@ namespace ttk { } // ---------------------------------------------------- - // Move nodes given scalars + // Scale pairs given persistence // ---------------------------------------------------- - Timer t_move; - printMsg("Move nodes given scalars", debug::Priority::VERBOSE); - - float const rootY = retVec[treeSimplexId[treeRoot] * 2 + 1]; - float const rootOriginY = retVec[treeSimplexId[treeRootOrigin] * 2 + 1]; - float const rootYmin = std::min(rootY, rootOriginY); - float const rootYmax = std::max(rootY, rootOriginY); - auto rootBirthDeath = tree->getBirthDeath(treeRoot); - const double rootBirth = std::get<0>(rootBirthDeath); - const double rootDeath = std::get<1>(rootBirthDeath); - for(size_t i = 0; i < tree->getNumberOfNodes(); ++i) { - retVec[treeSimplexId[i] * 2 + 1] - = (tree->getValue(i) - rootBirth) / (rootDeath - rootBirth); - retVec[treeSimplexId[i] * 2 + 1] - = retVec[treeSimplexId[i] * 2 + 1] * (rootYmax - rootYmin) + rootYmin; - } + // Sort leaves by branch depth + std::vector leaves; + tree->getLeavesFromTree(leaves); + std::vector allNodeLevel; + tree->getAllNodeLevel(allNodeLevel); + auto compLevel = [&](const ftm::idNode a, const ftm::idNode b) { + return allNodeLevel[tree->getNode(a)->getOrigin()] + > allNodeLevel[tree->getNode(b)->getOrigin()]; + }; + std::sort(leaves.begin(), leaves.end(), compLevel); - std::stringstream ss4; - ss4 << "MOVE SCALAR = " << t_move.getElapsedTime(); - printMsg(ss4.str(), debug::Priority::VERBOSE); + // Init some variables + float rootY = retVec[treeSimplexId[treeRoot] * 2 + 1]; + float rootOriginY = retVec[treeSimplexId[lowestNode] * 2 + 1]; + float rootYmin = std::min(rootY, rootOriginY); + float rootYmax = std::max(rootY, rootOriginY); - // ---------------------------------------------------- - // Scale pairs given persistence - // ---------------------------------------------------- Timer t_scale; printMsg("Scale pairs given persistence", debug::Priority::VERBOSE); - dataType rootPers = tree->getNodePersistence(treeRoot); - - std::vector leaves; - tree->getLeavesFromTree(leaves); - auto compLowerPers = [&](const ftm::idNode a, const ftm::idNode b) { - return tree->getNodePersistence(a) - < tree->getNodePersistence(b); - }; - std::sort(leaves.begin(), leaves.end(), compLowerPers); std::stack stack; for(auto node : leaves) stack.emplace(node); @@ -451,11 +578,9 @@ namespace ttk { ftm::idNode const node = stack.top(); stack.pop(); nodeDone[node] = true; - if(node == treeRoot or node == treeRootOrigin or tree->isNodeAlone(node)) continue; - dataType nodePers = tree->getNodePersistence(node); ftm::idNode const nodeOrigin = tree->getNode(node)->getOrigin(); @@ -467,7 +592,6 @@ namespace ttk { auto inc = sign * nodePers / rootPers * (rootYmax - rootYmin) / 2; retVec[treeSimplexId[node] * 2] = retVec[treeSimplexId[nodeOrigin] * 2] + inc; - // Push nodes in the branch to the stack ftm::idNode nodeParent = tree->getParentSafe(node); ftm::idNode oldNodeParent = -1; @@ -486,7 +610,6 @@ namespace ttk { } } } - // Manage saddle if(not tree->isLeaf(node) and not tree->isRoot(node)) { float const branchY @@ -495,7 +618,6 @@ namespace ttk { retVec[treeSimplexId[node] * 2] = branchY; } } - std::stringstream ss5; ss5 << "SCALE PERS. = " << t_scale.getElapsedTime(); printMsg(ss5.str(), debug::Priority::VERBOSE); @@ -506,6 +628,7 @@ namespace ttk { Timer t_avoid; printMsg("Avoid edges crossing", debug::Priority::VERBOSE); + // Init some variables bool isJT = tree->isJoinTree(); auto compValue = [&](const ftm::idNode a, const ftm::idNode b) { return (isJT @@ -519,8 +642,8 @@ namespace ttk { std::vector allBranchOriginsSize(tree->getNumberOfNodes()); std::queue queueCrossing; - // ----- Get important and non-important pairs gap and store saddle nodes - // of each branch + // ----- Get important and non-important pairs gap and store branch + // origins of each branch int maxSize = std::numeric_limits::lowest(); for(auto leaf : leaves) queueCrossing.emplace(leaf); @@ -673,11 +796,12 @@ namespace ttk { // ----- Correction of important/non-important pairs gap // TODO the gap between important pairs can be higher than the minimum gap // needed to avoid conflict. The gap is computed using the maximum number - // of non-important pairs attached to an inmportant pairs Unfortunately + // of non-important pairs attached to an important pairs. Unfortunately // the real gap can only be computed here, after the conflicts has been // avoided. The maximum real gap must be calculated and propagated to all // important branches and we also need to manage to avoid conflict with - // this new gap. Get real gap + // this new gap. + // Get real gap double realImportantPairsGap = std::numeric_limits::lowest(); /*if(customimportantPairsSpacing_) realImportantPairsGap = importantPairsGap; @@ -743,6 +867,15 @@ namespace ttk { ss6 << "AVOID CROSSING = " << t_avoid.getElapsedTime(); printMsg(ss6.str(), debug::Priority::VERBOSE); printMsg(debug::Separator::L2, debug::Priority::VERBOSE); + + // ---------------------------------------------------- + // Call Path Planar Layout if asked + // ---------------------------------------------------- + if(pathPlanarLayout_) { + pathPlanarLayout( + tree, retVec, treeSimplexId, leaves, importantPairsGap); + return; + } } template @@ -754,6 +887,9 @@ namespace ttk { treePlanarLayoutImpl(tree, oldBounds, refPersistence, res); } + // ======================================================================== + // Persistence Diagram Planar Layout + // ======================================================================== template void persistenceDiagramPlanarLayout(ftm::FTMTree_MT *tree, std::vector &res) { @@ -780,9 +916,9 @@ namespace ttk { } } - // ========================================================================== + // ======================================================================== // Bounds Utils - // ========================================================================== + // ======================================================================== void printTuple(std::tuple tup) { printMsg(debug::Separator::L2, debug::Priority::VERBOSE); std::stringstream ss; @@ -970,7 +1106,7 @@ namespace ttk { return std::make_tuple(x_min, x_max, y_min, y_max, z_min, z_max); } - // ========================================================================== + // ======================================================================== // Utils // ========================================================================== void parseExcludeImportantPairsString(std::string &excludeString, @@ -992,4 +1128,4 @@ namespace ttk { } }; -} // namespace ttk +} // namespace ttk \ No newline at end of file diff --git a/core/vtk/ttkMergeTreeClustering/ttkMergeTreeClustering.cpp b/core/vtk/ttkMergeTreeClustering/ttkMergeTreeClustering.cpp index 4453dee26e..c268bab645 100644 --- a/core/vtk/ttkMergeTreeClustering/ttkMergeTreeClustering.cpp +++ b/core/vtk/ttkMergeTreeClustering/ttkMergeTreeClustering.cpp @@ -1,8 +1,10 @@ +#include #include #include #include #include #include +#include #include #include #include @@ -128,11 +130,39 @@ int ttkMergeTreeClustering::RequestData(vtkInformation *ttkNotUsed(request), baseModule = 0; } - // filter out new backends (not yet supported) - if(baseModule != 0) { - printErr("Invalid Backend chosen. Path Mapping Distance and Branch Mapping " - "Distance not yet supported. Canceling computation."); - return 1; + // filter out backends (not yet supported) + if(baseModule == 1 && ComputeBarycenter) { + printErr("Invalid Backend chosen. Branch Mapping Distance not yet " + "supported for Barycenter computation. Canceling computation."); + return -1; + } + if(Backend == 1 && ComputeBarycenter) { + printErr("Invalid Backend chosen. Edit Distance is not yet supported for " + "Barycenter computation. Canceling computation."); + return -1; + } + if(Backend == 2) { + if(ComputeBarycenter) { + if(not BranchDecomposition) { + printErr( + "Invalid Backend chosen. Custom Backend without Branch " + "Decomposition is not yet supported for Barycenter computation. " + "Canceling computation."); + return -1; + } + if(KeepSubtree) { + printErr( + "Invalid Backend chosen. Custom Backend with Keep Subtree is not yet " + "supported for Barycenter computation. Canceling computation."); + return -1; + } + } + if(not BranchDecomposition and NormalizedWasserstein) { + printErr( + "Invalid Backend chosen. Custom Backend with Normalized Wasserstein " + "and without Branch Decomposition is not yet. Canceling computation."); + return -1; + } } // ------------------------------------------------------------------------------------ @@ -217,27 +247,18 @@ int ttkMergeTreeClustering::runCompute( BranchDecomposition = false; NormalizedWasserstein = false; KeepSubtree = true; - ComputeBarycenter = false; + } else if(Backend == 3) { + BranchDecomposition = false; + NormalizedWasserstein = false; + KeepSubtree = false; + } else if(Backend == 4) { + BranchDecomposition = false; + NormalizedWasserstein = false; + KeepSubtree = false; } if(IsPersistenceDiagram) { BranchDecomposition = true; } - if(ComputeBarycenter) { - if(not BranchDecomposition) - printMsg("BranchDecomposition is set to true since the barycenter " - "computation is asked."); - BranchDecomposition = true; - if(KeepSubtree) - printMsg("KeepSubtree is set to false since the barycenter computation " - "is asked."); - KeepSubtree = false; - } - if(not BranchDecomposition) { - if(NormalizedWasserstein) - printMsg("NormalizedWasserstein is set to false since branch " - "decomposition is not asked."); - NormalizedWasserstein = false; - } EpsilonTree2 = EpsilonTree1; Epsilon2Tree2 = Epsilon2Tree1; Epsilon3Tree2 = Epsilon3Tree1; @@ -247,32 +268,101 @@ int ttkMergeTreeClustering::runCompute( // Call base if(not ComputeBarycenter) { - MergeTreeDistance mergeTreeDistance; - mergeTreeDistance.setAssignmentSolver(AssignmentSolver); - mergeTreeDistance.setEpsilonTree1(EpsilonTree1); - mergeTreeDistance.setEpsilonTree2(EpsilonTree2); - mergeTreeDistance.setEpsilon2Tree1(Epsilon2Tree1); - mergeTreeDistance.setEpsilon2Tree2(Epsilon2Tree2); - mergeTreeDistance.setEpsilon3Tree1(Epsilon3Tree1); - mergeTreeDistance.setEpsilon3Tree2(Epsilon3Tree2); - mergeTreeDistance.setBranchDecomposition(BranchDecomposition); - mergeTreeDistance.setPersistenceThreshold(PersistenceThreshold); - mergeTreeDistance.setNormalizedWasserstein(NormalizedWasserstein); - mergeTreeDistance.setKeepSubtree(KeepSubtree); - mergeTreeDistance.setUseMinMaxPair(UseMinMaxPair); - mergeTreeDistance.setCleanTree(true); - mergeTreeDistance.setPostprocess(OutputTrees); - mergeTreeDistance.setDeleteMultiPersPairs(DeleteMultiPersPairs); - mergeTreeDistance.setEpsilon1UseFarthestSaddle(Epsilon1UseFarthestSaddle); - mergeTreeDistance.setIsPersistenceDiagram(IsPersistenceDiagram); - mergeTreeDistance.setNonMatchingWeight(NonMatchingWeight); - mergeTreeDistance.setThreadNumber(this->threadNumber_); - mergeTreeDistance.setDebugLevel(this->debugLevel_); - - distance = mergeTreeDistance.execute( - intermediateMTrees[0], intermediateMTrees[1], outputMatching); - trees1NodeCorrMesh = mergeTreeDistance.getTreesNodeCorr(); - finalDistances = std::vector{distance}; + if(baseModule == 0) { + MergeTreeDistance mergeTreeDistance; + mergeTreeDistance.setAssignmentSolver(AssignmentSolver); + mergeTreeDistance.setEpsilonTree1(EpsilonTree1); + mergeTreeDistance.setEpsilonTree2(EpsilonTree2); + mergeTreeDistance.setEpsilon2Tree1(Epsilon2Tree1); + mergeTreeDistance.setEpsilon2Tree2(Epsilon2Tree2); + mergeTreeDistance.setEpsilon3Tree1(Epsilon3Tree1); + mergeTreeDistance.setEpsilon3Tree2(Epsilon3Tree2); + mergeTreeDistance.setBranchDecomposition(BranchDecomposition); + mergeTreeDistance.setPersistenceThreshold(PersistenceThreshold); + mergeTreeDistance.setNormalizedWasserstein(NormalizedWasserstein); + mergeTreeDistance.setKeepSubtree(KeepSubtree); + mergeTreeDistance.setUseMinMaxPair(UseMinMaxPair); + mergeTreeDistance.setCleanTree(true); + mergeTreeDistance.setPostprocess(OutputTrees); + mergeTreeDistance.setDeleteMultiPersPairs(DeleteMultiPersPairs); + mergeTreeDistance.setEpsilon1UseFarthestSaddle(Epsilon1UseFarthestSaddle); + mergeTreeDistance.setIsPersistenceDiagram(IsPersistenceDiagram); + mergeTreeDistance.setNonMatchingWeight(NonMatchingWeight); + mergeTreeDistance.setThreadNumber(this->threadNumber_); + mergeTreeDistance.setDebugLevel(this->debugLevel_); + + distance = mergeTreeDistance.execute( + intermediateMTrees[0], intermediateMTrees[1], outputMatching); + trees1NodeCorrMesh = mergeTreeDistance.getTreesNodeCorr(); + finalDistances = std::vector{distance}; + } else if(baseModule == 1) { + BranchMappingDistance branchDist; + branchDist.setBaseMetric(branchMetric); + branchDist.setAssignmentSolver(AssignmentSolver); + branchDist.setSquared(false); + branchDist.setComputeMapping(true); + branchDist.setPreprocess(true); + branchDist.setBranchDecomposition(false); + // branchDist.setWriteBD(false); + branchDist.setWriteBD(true); + + branchDist.setEpsilonTree1(EpsilonTree1); + branchDist.setEpsilonTree2(EpsilonTree2); + branchDist.setPersistenceThreshold(PersistenceThreshold); + // branchDist.setUseMinMaxPair(UseMinMaxPair); + branchDist.setCleanTree(true); + branchDist.setDeleteMultiPersPairs(DeleteMultiPersPairs); + // branchDist.setEpsilon1UseFarthestSaddle(Epsilon1UseFarthestSaddle); + branchDist.setPersistenceThreshold(PersistenceThreshold); + branchDist.setThreadNumber(this->threadNumber_); + branchDist.setDebugLevel(this->debugLevel_); + + distance = branchDist.execute( + intermediateMTrees[0], intermediateMTrees[1], &outputMatching); + + std::vector nodeCorr1( + intermediateTrees[0]->getNumberOfNodes()); + std::vector nodeCorr2( + intermediateTrees[1]->getNumberOfNodes()); + for(unsigned int i = 0; i < nodeCorr1.size(); i++) + nodeCorr1[i] = i; + for(unsigned int i = 0; i < nodeCorr2.size(); i++) + nodeCorr2[i] = i; + trees1NodeCorrMesh = branchDist.getTreesNodeCorr(); + finalDistances = std::vector{distance}; + } else { + PathMappingDistance pathDist; + pathDist.setBaseMetric(pathMetric); + pathDist.setAssignmentSolver(AssignmentSolver); + pathDist.setSquared(false); + pathDist.setComputeMapping(true); + pathDist.setPreprocess(true); + pathDist.setBranchDecomposition(false); + + pathDist.setEpsilonTree1(EpsilonTree1); + pathDist.setEpsilonTree2(EpsilonTree2); + pathDist.setPersistenceThreshold(PersistenceThreshold); + // pathDist.setUseMinMaxPair(UseMinMaxPair); + pathDist.setCleanTree(true); + pathDist.setDeleteMultiPersPairs(DeleteMultiPersPairs); + // pathDist.setEpsilon1UseFarthestSaddle(Epsilon1UseFarthestSaddle); + pathDist.setPersistenceThreshold(PersistenceThreshold); + pathDist.setThreadNumber(this->threadNumber_); + pathDist.setDebugLevel(this->debugLevel_); + + distance = pathDist.execute( + intermediateMTrees[0], intermediateMTrees[1], &outputMatching); + trees1NodeCorrMesh = pathDist.getTreesNodeCorr(); + + // std::vector + // nodeCorr1(intermediateTrees[0]->getNumberOfNodes()); + // std::vector + // nodeCorr2(intermediateTrees[1]->getNumberOfNodes()); for(ttk::SimplexId + // i=0; i>{nodeCorr1,nodeCorr2}; + finalDistances = std::vector{distance}; + } } else { if(NumberOfBarycenters == 1) { // and numInputs2==0){ MergeTreeBarycenter mergeTreeBarycenter; @@ -283,31 +373,51 @@ int ttkMergeTreeClustering::runCompute( mergeTreeBarycenter.setEpsilon2Tree2(Epsilon2Tree2); mergeTreeBarycenter.setEpsilon3Tree1(Epsilon3Tree1); mergeTreeBarycenter.setEpsilon3Tree2(Epsilon3Tree2); - mergeTreeBarycenter.setBranchDecomposition(BranchDecomposition); - mergeTreeBarycenter.setPersistenceThreshold(PersistenceThreshold); - mergeTreeBarycenter.setNormalizedWasserstein(NormalizedWasserstein); - mergeTreeBarycenter.setKeepSubtree(KeepSubtree); - mergeTreeBarycenter.setUseMinMaxPair(UseMinMaxPair); - mergeTreeBarycenter.setAddNodes(AddNodes); - mergeTreeBarycenter.setDeterministic(Deterministic); - mergeTreeBarycenter.setBarycenterSizeLimitPercent( - BarycenterSizeLimitPercent); - mergeTreeBarycenter.setAlpha(Alpha); - mergeTreeBarycenter.setPostprocess(OutputTrees); - mergeTreeBarycenter.setDeleteMultiPersPairs(DeleteMultiPersPairs); - mergeTreeBarycenter.setEpsilon1UseFarthestSaddle( - Epsilon1UseFarthestSaddle); - mergeTreeBarycenter.setIsPersistenceDiagram(IsPersistenceDiagram); mergeTreeBarycenter.setThreadNumber(this->threadNumber_); mergeTreeBarycenter.setDebugLevel(this->debugLevel_); + mergeTreeBarycenter.setBaseModule(this->baseModule); + mergeTreeBarycenter.setIsPersistenceDiagram(IsPersistenceDiagram); + mergeTreeBarycenter.setAlpha(Alpha); + mergeTreeBarycenter.setDeterministic(Deterministic); + mergeTreeBarycenter.setPersistenceThreshold(PersistenceThreshold); + mergeTreeBarycenter.setUseFixedInit(useFixedInit); + mergeTreeBarycenter.setFixedInitNumber(fixedInitNumber); + // mergeTreeBarycenter.setUseEarlyOut(useEarlyOut); + mergeTreeBarycenter.setIterationLimit(iterationLimit); + if(baseModule == 2) { + mergeTreeBarycenter.setPathMetric(this->pathMetric); + mergeTreeBarycenter.setBranchDecomposition(false); + mergeTreeBarycenter.setNormalizedWasserstein(false); + mergeTreeBarycenter.setKeepSubtree(false); + // mergeTreeBarycenter.setUseMinMaxPair(false); + mergeTreeBarycenter.setAddNodes(false); + mergeTreeBarycenter.setPostprocess(false); + mergeTreeBarycenter.setUseMedianBarycenter(useMedianBarycenter); + } else { + mergeTreeBarycenter.setBranchDecomposition(BranchDecomposition); + mergeTreeBarycenter.setNormalizedWasserstein(NormalizedWasserstein); + mergeTreeBarycenter.setKeepSubtree(KeepSubtree); + mergeTreeBarycenter.setUseMinMaxPair(UseMinMaxPair); + mergeTreeBarycenter.setAddNodes(AddNodes); + mergeTreeBarycenter.setBarycenterSizeLimitPercent( + BarycenterSizeLimitPercent); + mergeTreeBarycenter.setPostprocess(OutputTrees); + mergeTreeBarycenter.setDeleteMultiPersPairs(DeleteMultiPersPairs); + mergeTreeBarycenter.setEpsilon1UseFarthestSaddle( + Epsilon1UseFarthestSaddle); + } mergeTreeBarycenter.execute( - intermediateMTrees, outputMatchingBarycenter[0], barycenters[0]); + intermediateMTrees, outputMatchingBarycenter[0], + outputMatchings_path[0], barycenters[0]); trees1NodeCorrMesh = mergeTreeBarycenter.getTreesNodeCorr(); finalDistances = mergeTreeBarycenter.getFinalDistances(); + } else { MergeTreeClustering mergeTreeClustering; mergeTreeClustering.setAssignmentSolver(AssignmentSolver); + mergeTreeClustering.setBaseModule(this->baseModule); + mergeTreeClustering.setPathMetric(this->pathMetric); mergeTreeClustering.setEpsilonTree1(EpsilonTree1); mergeTreeClustering.setEpsilonTree2(EpsilonTree2); mergeTreeClustering.setEpsilon2Tree1(Epsilon2Tree1); @@ -507,6 +617,7 @@ int ttkMergeTreeClustering::runOutput( visuMaker.setOutputSegmentation(OutputSegmentation); visuMaker.setDimensionSpacing(DimensionSpacing); visuMaker.setDimensionToShift(DimensionToShift); + visuMaker.setDimensionsShift(XShift, YShift, ZShift); visuMaker.setImportantPairs(ImportantPairs); visuMaker.setMaximumImportantPairs(MaximumImportantPairs); visuMaker.setMinimumImportantPairs(MinimumImportantPairs); @@ -516,6 +627,8 @@ int ttkMergeTreeClustering::runOutput( visuMaker.setExcludeImportantPairsHigher(ExcludeImportantPairsHigher); visuMaker.setExcludeImportantPairsLower(ExcludeImportantPairsLower); visuMaker.setIsPersistenceDiagram(IsPersistenceDiagram); + visuMaker.setPathPlanarLayout(PathPlanarLayout); + // visuMaker.setPathPlanarLayout(baseModule == 2); nodeCorr.clear(); // First tree @@ -620,6 +733,8 @@ int ttkMergeTreeClustering::runOutput( visuMakerMatching.setVtkOutputNode1(vtkOutputNode2); visuMakerMatching.setNodeCorr1(nodeCorr); visuMakerMatching.setDebugLevel(this->debugLevel_); + visuMakerMatching.setPathPlanarLayout(PathPlanarLayout); + // visuMakerMatching.setPathPlanarLayout(baseModule == 2); visuMakerMatching.makeMatchingOutput(tree1, tree2); @@ -671,9 +786,9 @@ int ttkMergeTreeClustering::runOutput( } } for(unsigned int c = 0; c < NumberOfBarycenters; ++c) { -#ifdef TTK_ENABLE_OPENMP -#pragma omp parallel for schedule(dynamic) num_threads(this->threadNumber_) -#endif + /*#ifdef TTK_ENABLE_OPENMP + #pragma omp parallel for schedule(dynamic) + num_threads(this->threadNumber_) #endif*/ for(int i = 0; i < numInputs; ++i) { if(clusteringAssignment[i] != (int)c) continue; @@ -696,6 +811,7 @@ int ttkMergeTreeClustering::runOutput( visuMaker.setOutputSegmentation(OutputSegmentation); visuMaker.setDimensionSpacing(DimensionSpacing); visuMaker.setDimensionToShift(DimensionToShift); + visuMaker.setDimensionsShift(XShift, YShift, ZShift); visuMaker.setImportantPairs(ImportantPairs); visuMaker.setMaximumImportantPairs(MaximumImportantPairs); visuMaker.setMinimumImportantPairs(MinimumImportantPairs); @@ -717,11 +833,15 @@ int ttkMergeTreeClustering::runOutput( visuMaker.setVtkOutputSegmentation(vtkOutputSegmentation); visuMaker.setClusteringAssignment(clusteringAssignment); visuMaker.setOutputMatchingBarycenter(outputMatchingBarycenter); + visuMaker.setPathMatchings(outputMatchings_path); visuMaker.setPrintTreeId(i); visuMaker.setPrintClusterId(c); visuMaker.setDebugLevel(this->debugLevel_); visuMaker.setIsPersistenceDiagram(IsPersistenceDiagram); visuMaker.setIsPDSadMax(JoinSplitMixtureCoefficient == 0); + visuMaker.setPathPlanarLayout(PathPlanarLayout); + visuMaker.setEnableBarycenterAlignment(baseModule == 2); + // visuMaker.setPathPlanarLayout(baseModule == 2); visuMaker.makeTreesOutput( intermediateTrees, barycentersTree); @@ -807,6 +927,7 @@ int ttkMergeTreeClustering::runOutput( visuMakerBary.setOutputSegmentation(false); visuMakerBary.setDimensionSpacing(DimensionSpacing); visuMakerBary.setDimensionToShift(DimensionToShift); + visuMakerBary.setDimensionsShift(XShift, YShift, ZShift); visuMakerBary.setImportantPairs(ImportantPairs); visuMakerBary.setMaximumImportantPairs(MaximumImportantPairs); visuMakerBary.setMinimumImportantPairs(MinimumImportantPairs); @@ -829,6 +950,7 @@ int ttkMergeTreeClustering::runOutput( visuMakerBary.setVtkOutputSegmentation(vtkOutputSegmentation); visuMakerBary.setClusteringAssignment(clusteringAssignment); visuMakerBary.setOutputMatchingBarycenter(outputMatchingBarycenter); + visuMakerBary.setPathMatchings(outputMatchings_path); visuMakerBary.setPrintTreeId(c); visuMakerBary.setPrintClusterId(c); if(numInputs == 2 and NumberOfBarycenters == 1) { @@ -838,6 +960,9 @@ int ttkMergeTreeClustering::runOutput( visuMakerBary.setDebugLevel(this->debugLevel_); visuMakerBary.setIsPersistenceDiagram(IsPersistenceDiagram); visuMakerBary.setIsPDSadMax(JoinSplitMixtureCoefficient == 0); + visuMakerBary.setPathPlanarLayout(PathPlanarLayout); + visuMakerBary.setEnableBarycenterAlignment(baseModule == 2); + // visuMakerBary.setPathPlanarLayout(baseModule == 2); visuMakerBary.makeTreesOutput( intermediateTrees, barycentersTree); @@ -913,6 +1038,8 @@ int ttkMergeTreeClustering::runOutput( visuMakerMatching.setPrintTreeId(i); visuMakerMatching.setPrintClusterId(c); visuMakerMatching.setDebugLevel(this->debugLevel_); + visuMakerMatching.setPathPlanarLayout(PathPlanarLayout); + // visuMakerMatching.setPathPlanarLayout(baseModule == 2); visuMakerMatching.makeMatchingOutput( intermediateTrees, barycentersTree); diff --git a/core/vtk/ttkMergeTreeClustering/ttkMergeTreeClustering.h b/core/vtk/ttkMergeTreeClustering/ttkMergeTreeClustering.h index 83cfe315d2..25fe17f622 100644 --- a/core/vtk/ttkMergeTreeClustering/ttkMergeTreeClustering.h +++ b/core/vtk/ttkMergeTreeClustering/ttkMergeTreeClustering.h @@ -1,6 +1,7 @@ /// \ingroup vtk /// \class ttkMergeTreeClustering /// \author Mathieu Pont (mathieu.pont@lip6.fr) +/// \author Florian Wetzels (wetzels@cs.uni-kl.de) /// \date 2021. /// /// \brief TTK VTK-filter that wraps the ttk::MergeTreeClustering module. @@ -25,6 +26,12 @@ /// Mathieu Pont, Jules Vidal, Julie Delon, Julien Tierny.\n /// Proc. of IEEE VIS 2021.\n /// IEEE Transactions on Visualization and Computer Graphics, 2021 + +/// \b Related \b publication \n +/// "Merge Tree Geodesics and Barycenters with Path Mappings" \n +/// F. Wetzels, M. Pont, J. Tierny and C. Garth.\n +/// Proc. of IEEE VIS 2023.\n +/// IEEE Transactions on Visualization and Computer Graphics, 2024 /// /// \sa ttk::MergeTreeClustering /// \sa ttkAlgorithm @@ -93,12 +100,18 @@ class TTKMERGETREECLUSTERING_EXPORT ttkMergeTreeClustering bool oldKS = KeepSubtree; double JoinSplitMixtureCoefficient = 0.5; bool ComputeBarycenter = false; + bool oldComputeBarycenter = ComputeBarycenter; unsigned int NumberOfBarycenters = 1; double BarycenterSizeLimitPercent = 0.0; bool Deterministic = false; int pathMetric = 0; int branchMetric = 0; int baseModule = 0; + bool useMedianBarycenter = false; + bool useFixedInit = false; + int fixedInitNumber = 0; + // bool useEarlyOut = true; + int iterationLimit = 100; double NonMatchingWeight = 1.0; // Output Options @@ -106,10 +119,14 @@ class TTKMERGETREECLUSTERING_EXPORT ttkMergeTreeClustering bool OutputSegmentation = false; bool PlanarLayout = false; bool BranchDecompositionPlanarLayout = false; + bool PathPlanarLayout = false; double BranchSpacing = 1.; bool RescaleTreesIndividually = false; double DimensionSpacing = 1.; int DimensionToShift = 0; + double XShift = 1.0; + double YShift = 0.0; + double ZShift = 0.0; double ImportantPairs = 50.; int MaximumImportantPairs = 0; int MinimumImportantPairs = 0; @@ -136,6 +153,10 @@ class TTKMERGETREECLUSTERING_EXPORT ttkMergeTreeClustering std::vector>>> outputMatchingBarycenter, outputMatchingBarycenter2; + std::vector, + std::pair>>>> + outputMatchings_path; // Barycenter std::vector> barycentersS, barycentersS2; @@ -165,13 +186,20 @@ class TTKMERGETREECLUSTERING_EXPORT ttkMergeTreeClustering std::vector< std::vector>>( numInputs)); - outputMatchingBarycenter2 = std::vector>>>( NumberOfBarycenters, std::vector< std::vector>>( numInputs2)); + outputMatchings_path = std::vector, + std::pair>>>>( + NumberOfBarycenters, + std::vector< + std::vector, + std::pair>>>( + numInputs)); // Barycenter barycentersS @@ -257,6 +285,12 @@ class TTKMERGETREECLUSTERING_EXPORT ttkMergeTreeClustering NormalizedWasserstein = oldNW; KeepSubtree = oldKS; } + if(Backend == 1 or Backend == 3) // edit distance or branch mapping + ComputeBarycenter = oldComputeBarycenter; + if(newBackend == 1 or newBackend == 3) { + oldComputeBarycenter = ComputeBarycenter; + ComputeBarycenter = false; + } Backend = newBackend; Modified(); resetDataVisualization(); @@ -338,11 +372,43 @@ class TTKMERGETREECLUSTERING_EXPORT ttkMergeTreeClustering void SetBranchMetric(int m) { branchMetric = m; Modified(); + resetDataVisualization(); } void SetPathMetric(int m) { pathMetric = m; Modified(); + resetDataVisualization(); + } + + void SetUseMedianBarycenter(bool useMedian) { + useMedianBarycenter = useMedian; + Modified(); + resetDataVisualization(); + } + + void SetUseFixedInit(bool ufi) { + useFixedInit = ufi; + Modified(); + resetDataVisualization(); + } + + void SetFixedInitNumber(int fi) { + fixedInitNumber = fi; + Modified(); + resetDataVisualization(); + } + + // void SetUseEarlyOut(bool eo) { + // useEarlyOut = eo; + // Modified(); + // resetDataVisualization(); + // } + + void SetIterationLimit(int l) { + iterationLimit = l; + Modified(); + resetDataVisualization(); } void SetNonMatchingWeight(double weight) { @@ -368,6 +434,9 @@ class TTKMERGETREECLUSTERING_EXPORT ttkMergeTreeClustering vtkSetMacro(BranchDecompositionPlanarLayout, bool); vtkGetMacro(BranchDecompositionPlanarLayout, bool); + vtkSetMacro(PathPlanarLayout, bool); + vtkGetMacro(PathPlanarLayout, bool); + vtkSetMacro(BranchSpacing, double); vtkGetMacro(BranchSpacing, double); @@ -380,6 +449,15 @@ class TTKMERGETREECLUSTERING_EXPORT ttkMergeTreeClustering vtkSetMacro(DimensionToShift, int); vtkGetMacro(DimensionToShift, int); + vtkSetMacro(XShift, double); + vtkGetMacro(XShift, double); + + vtkSetMacro(YShift, double); + vtkGetMacro(YShift, double); + + vtkSetMacro(ZShift, double); + vtkGetMacro(ZShift, double); + vtkSetMacro(ImportantPairs, double); vtkGetMacro(ImportantPairs, double); diff --git a/core/vtk/ttkMergeTreeDistanceMatrix/ttkMergeTreeDistanceMatrix.cpp b/core/vtk/ttkMergeTreeDistanceMatrix/ttkMergeTreeDistanceMatrix.cpp index 0411b3480b..cd37569c36 100644 --- a/core/vtk/ttkMergeTreeDistanceMatrix/ttkMergeTreeDistanceMatrix.cpp +++ b/core/vtk/ttkMergeTreeDistanceMatrix/ttkMergeTreeDistanceMatrix.cpp @@ -173,6 +173,9 @@ int ttkMergeTreeDistanceMatrix::run( metric = "Shifting cost"; else return 1; + epsilonTree2_ = epsilonTree1_; + epsilon2Tree2_ = epsilon2Tree1_; + epsilon3Tree2_ = epsilon3Tree1_; printMsg("BranchMetric: " + metric); } if(baseModule_ == 2) { @@ -182,16 +185,16 @@ int ttkMergeTreeDistanceMatrix::run( metric = "Persistence difference"; else return 1; + epsilonTree2_ = epsilonTree1_; + epsilon2Tree2_ = epsilon2Tree1_; + epsilon3Tree2_ = epsilon3Tree1_; printMsg("PathMetric: " + metric); } // --- Call base std::vector> treesDistMat( numInputs, std::vector(numInputs)); - if(baseModule_ == 0) - execute(intermediateTrees, intermediateTrees2, treesDistMat); - else - execute(intermediateTrees, treesDistMat); + execute(intermediateTrees, intermediateTrees2, treesDistMat); // --- Create output auto treesDistTable = vtkTable::GetData(outputVector); diff --git a/core/vtk/ttkMergeTreeDistanceMatrix/ttkMergeTreeDistanceMatrix.h b/core/vtk/ttkMergeTreeDistanceMatrix/ttkMergeTreeDistanceMatrix.h index 8ca345162c..5909a2a8b4 100644 --- a/core/vtk/ttkMergeTreeDistanceMatrix/ttkMergeTreeDistanceMatrix.h +++ b/core/vtk/ttkMergeTreeDistanceMatrix/ttkMergeTreeDistanceMatrix.h @@ -1,6 +1,7 @@ /// \ingroup vtk /// \class ttkMergeTreeDistanceMatrix /// \author Mathieu Pont +/// \author Florian Wetzels (wetzels@cs.uni-kl.de) /// \date 2021. /// /// \brief TTK VTK-filter that wraps the ttk::MergeTreeDistanceMatrix module. @@ -17,6 +18,27 @@ /// See the related ParaView example state files for usage examples within a /// VTK pipeline. /// +/// \b Related \b publication \n +/// "Wasserstein Distances, Geodesics and Barycenters of Merge Trees" \n +/// Mathieu Pont, Jules Vidal, Julie Delon, Julien Tierny.\n +/// Proc. of IEEE VIS 2021.\n +/// IEEE Transactions on Visualization and Computer Graphics, 2021 +/// +/// \b Related \b publication \n +/// "Edit Distance between Merge Trees" \n +/// R. Sridharamurthy, T. B. Masood, A. Kamakshidasan and V. Natarajan. \n +/// IEEE Transactions on Visualization and Computer Graphics, 2020. +/// +/// \b Related \b publication \n +/// "Branch Decomposition-Independent Edit Distances for Merge Trees." \n +/// Florian Wetzels, Heike Leitte, and Christoph Garth. \n +/// Computer Graphics Forum, 2022. +/// +/// \b Related \b publication \n +/// "A Deformation-based Edit Distance for Merge Trees" \n +/// Florian Wetzels, Christoph Garth. \n +/// TopoInVis 2022. +/// /// \sa ttk::MergeTreeDistanceMatrix /// \sa ttkAlgorithm /// diff --git a/core/vtk/ttkMergeTreeTemporalReduction/ttkMergeTreeTemporalReduction.h b/core/vtk/ttkMergeTreeTemporalReduction/ttkMergeTreeTemporalReduction.h index 9dac57f85f..a6646c4dce 100644 --- a/core/vtk/ttkMergeTreeTemporalReduction/ttkMergeTreeTemporalReduction.h +++ b/core/vtk/ttkMergeTreeTemporalReduction/ttkMergeTreeTemporalReduction.h @@ -1,6 +1,7 @@ /// \ingroup vtk /// \class ttkMergeTreeTemporalReduction /// \author Mathieu Pont (mathieu.pont@lip6.fr) +/// \author Florian Wetzels (wetzels@cs.uni-kl.de) /// \date 2021. /// /// \brief TTK VTK-filter that wraps the ttk::MergeTreeTemporalReduction @@ -27,6 +28,12 @@ /// Mathieu Pont, Jules Vidal, Julie Delon, Julien Tierny.\n /// Proc. of IEEE VIS 2021.\n /// IEEE Transactions on Visualization and Computer Graphics, 2021 + +/// \b Related \b publication \n +/// "Merge Tree Geodesics and Barycenters with Path Mappings" \n +/// F. Wetzels, M. Pont, J. Tierny and C. Garth.\n +/// Proc. of IEEE VIS 2023.\n +/// IEEE Transactions on Visualization and Computer Graphics, 2024 /// /// \b Online \b examples: \n /// - >>> outputMatchingBarycenter; + // Path layout + std::vector, + std::pair>>>> + pathMatchings; + // Barycenter output std::vector> allBaryPercentMatch; @@ -119,6 +129,20 @@ class ttkMergeTreeVisualization : public ttk::MergeTreeVisualization { void setDimensionToShift(int i) { DimensionToShift = i; } + void setXshift(double shift) { + XShift = shift; + } + void setYshift(double shift) { + YShift = shift; + } + void setZshift(double shift) { + ZShift = shift; + } + void setDimensionsShift(double xShift, double yShift, double zShift) { + setXshift(xShift); + setYshift(yShift); + setZshift(zShift); + } void setOutputSegmentation(bool b) { OutputSegmentation = b; } @@ -146,6 +170,10 @@ class ttkMergeTreeVisualization : public ttk::MergeTreeVisualization { isPDSadMax = isSadMax; } + void setEnableBarycenterAlignment(bool eba) { + enableBarycenterAlignment = eba; + } + // Offset void setISampleOffset(int offset) { iSampleOffset = offset; @@ -208,6 +236,68 @@ class ttkMergeTreeVisualization : public ttk::MergeTreeVisualization { outputMatchingBarycenter = matching; } + // Path layout + void setPathMatchings( + std::vector, + std::pair>>>> + &matchings) { + pathMatchings = matchings; + } + void getTreePathing( + FTMTree_MT *tree, + std::vector< + std::vector, + std::pair>>> + &matchings, + bool isFirstTree, + std::vector &pathing, + std::vector &pathingID) { + pathing = std::vector(tree->getNumberOfNodes()); + pathingID = std::vector(tree->getNumberOfNodes(), -1); + std::vector nodeLevel; + tree->getAllNodeLevel(nodeLevel); + int pathID = 0; + std::vector> processed( + tree->getNumberOfNodes(), + std::vector(tree->getNumberOfNodes(), false)); + for(auto &matching : matchings) { + for(auto &match : matching) { + auto first = (isFirstTree ? match.first.first : match.second.first); + auto second = (isFirstTree ? match.first.second : match.second.second); + auto lowest = (nodeLevel[first] < nodeLevel[second] ? second : first); + auto highest = (nodeLevel[first] < nodeLevel[second] ? first : second); + if(processed[lowest][highest]) + continue; + processed[lowest][highest] = true; + while(lowest != highest) { + pathing[lowest] = highest; + pathingID[lowest] = pathID; + lowest = tree->getParentSafe(lowest); + } + if(tree->isRoot(highest)) { + pathing[highest] = highest; + pathingID[highest] = pathID; + } + ++pathID; + } + } + } + void getTreePathing( + FTMTree_MT *tree, + std::vector, + std::pair>> + &matching, + bool isFirstTree, + std::vector &pathing, + std::vector &pathingID) { + std::vector< + std::vector, + std::pair>>> + matchings{matching}; + getTreePathing(tree, matchings, isFirstTree, pathing, pathingID); + } + // Barycenter output std::vector> getAllBaryPercentMatch() { return allBaryPercentMatch; @@ -432,6 +522,12 @@ class ttkMergeTreeVisualization : public ttk::MergeTreeVisualization { tree1NodeIdField->SetName("tree1NodeId"); vtkNew tree2NodeIdField{}; tree2NodeIdField->SetName("tree2NodeId"); + vtkNew mergeTree1NodeIdField{}; + mergeTree1NodeIdField->SetName("mergeTree1NodeId"); + vtkNew mergeTree2NodeIdField{}; + mergeTree2NodeIdField->SetName("mergeTree2NodeId"); + vtkNew isBarycenterNodeField{}; + isBarycenterNodeField->SetName("isBarycenterNode"); vtkNew matchingPercentMatch{}; matchingPercentMatch->SetName("MatchingPercentMatch"); @@ -468,6 +564,10 @@ class ttkMergeTreeVisualization : public ttk::MergeTreeVisualization { : nodeCorr1[0][tree1NodeId]; double *point1 = vtkOutputNode2->GetPoints()->GetPoint(pointToGet1); const SimplexId nextPointId1 = pointsM->InsertNextPoint(point1); + if(not clusteringOutput) + isBarycenterNodeField->InsertNextTuple1(0); + else + isBarycenterNodeField->InsertNextTuple1(1); pointIds[0] = nextPointId1; // Get second point @@ -477,6 +577,7 @@ class ttkMergeTreeVisualization : public ttk::MergeTreeVisualization { : nodeCorr1[1][tree2NodeId]; double *point2 = vtkOutputNode1->GetPoints()->GetPoint(pointToGet2); const SimplexId nextPointId2 = pointsM->InsertNextPoint(point2); + isBarycenterNodeField->InsertNextTuple1(0); pointIds[1] = nextPointId2; // Add cell @@ -495,6 +596,8 @@ class ttkMergeTreeVisualization : public ttk::MergeTreeVisualization { "// Add tree1 and tree2 node ids", ttk::debug::Priority::VERBOSE); tree1NodeIdField->InsertNextTuple1(pointToGet1); tree2NodeIdField->InsertNextTuple1(pointToGet2); + mergeTree1NodeIdField->InsertNextTuple1(tree1NodeId); + mergeTree2NodeIdField->InsertNextTuple1(tree2NodeId); // Add matching ID matchingID->InsertNextTuple1(count); @@ -543,6 +646,9 @@ class ttkMergeTreeVisualization : public ttk::MergeTreeVisualization { vtkMatching->GetCellData()->AddArray(costArray); vtkMatching->GetCellData()->AddArray(tree1NodeIdField); vtkMatching->GetCellData()->AddArray(tree2NodeIdField); + vtkMatching->GetCellData()->AddArray(mergeTree1NodeIdField); + vtkMatching->GetCellData()->AddArray(mergeTree2NodeIdField); + vtkMatching->GetPointData()->AddArray(isBarycenterNodeField); if(allBaryPercentMatch.size() != 0) vtkMatching->GetCellData()->AddArray(matchingPercentMatch); vtkOutputMatching->ShallowCopy(vtkMatching); @@ -583,6 +689,8 @@ class ttkMergeTreeVisualization : public ttk::MergeTreeVisualization { bool const clusteringOutput = (NumberOfBarycenters != 0); NumberOfBarycenters = std::max(NumberOfBarycenters, 1); // to always enter the outer loop + bool alignTrees = trees.size() == 2 and barycenters.size() == 1 + and enableBarycenterAlignment; bool const embeddedDiagram = not PlanarLayout and isPersistenceDiagram; // TreeNodeIdRev @@ -625,13 +733,19 @@ class ttkMergeTreeVisualization : public ttk::MergeTreeVisualization { std::vector> allBaryBounds(barycenters.size()); - std::vector> allBaryBranching(barycenters.size()); - std::vector> allBaryBranchingID(barycenters.size()); + std::vector> allBaryBranching(barycenters.size()), + allBaryPathing(barycenters.size()); + std::vector> allBaryBranchingID(barycenters.size()), + allBaryPathingID(barycenters.size()); for(size_t c = 0; c < barycenters.size(); ++c) { allBaryBounds[c] = getMaximalBounds(allBounds, clusteringAssignment, c); if(not isPersistenceDiagram) barycenters[c]->getTreeBranching( allBaryBranching[c], allBaryBranchingID[c]); + if(pathPlanarLayout_ and !pathMatchings.empty() + and !pathMatchings[c].empty()) + getTreePathing(barycenters[c], pathMatchings[c], true, + allBaryPathing[c], allBaryPathingID[c]); } if(not clusteringOutput) allBaryBounds.emplace_back( @@ -666,6 +780,8 @@ class ttkMergeTreeVisualization : public ttk::MergeTreeVisualization { isDummyNode->SetName("isDummyNode"); vtkNew branchNodeID{}; branchNodeID->SetName("BranchNodeID"); + vtkNew pathNodeID{}; + pathNodeID->SetName("PathNodeID"); vtkNew scalar{}; scalar->SetName("Scalar"); vtkNew isImportantPairsNode{}; @@ -682,6 +798,8 @@ class ttkMergeTreeVisualization : public ttk::MergeTreeVisualization { treeIDNode->SetName("TreeID"); vtkNew branchBaryNodeID{}; branchBaryNodeID->SetName("BranchBaryNodeID"); + vtkNew pathBaryNodeID{}; + pathBaryNodeID->SetName("PathBaryNodeID"); vtkNew isInterpolatedTreeNode{}; isInterpolatedTreeNode->SetName("isInterpolatedTree"); @@ -723,6 +841,8 @@ class ttkMergeTreeVisualization : public ttk::MergeTreeVisualization { isDummyArc->SetName("isDummyArc"); vtkNew branchID{}; branchID->SetName("BranchID"); + vtkNew pathID{}; + pathID->SetName("PathID"); vtkNew upNodeId{}; upNodeId->SetName("upNodeId"); vtkNew downNodeId{}; @@ -732,6 +852,8 @@ class ttkMergeTreeVisualization : public ttk::MergeTreeVisualization { treeIDArc->SetName((isPersistenceDiagram ? "DiagramID" : "TreeID")); vtkNew branchBaryID{}; branchBaryID->SetName("BranchBaryNodeID"); + vtkNew pathBaryID{}; + pathBaryID->SetName("PathBaryNodeID"); vtkNew isInterpolatedTreeArc{}; isInterpolatedTreeArc->SetName("isInterpolatedTree"); @@ -838,29 +960,34 @@ class ttkMergeTreeVisualization : public ttk::MergeTreeVisualization { continue; // Manage important pairs threshold - importantPairs_ = importantPairsOriginal; - if(MaximumImportantPairs > 0 or MinimumImportantPairs > 0) { - std::vector> pairs; - trees[i]->getPersistencePairsFromTree(pairs, false); - if(MaximumImportantPairs > 0) { - int firstIndex = pairs.size() - MaximumImportantPairs; - firstIndex - = std::max(std::min(firstIndex, int(pairs.size()) - 1), 0); - double tempThreshold = 0.999 * std::get<2>(pairs[firstIndex]) - / std::get<2>(pairs[pairs.size() - 1]); - tempThreshold *= 100; - importantPairs_ = std::max(importantPairs_, tempThreshold); - } - if(MinimumImportantPairs > 0) { - int firstIndex = pairs.size() - MinimumImportantPairs; - firstIndex - = std::max(std::min(firstIndex, int(pairs.size()) - 1), 0); - double tempThreshold = 0.999 * std::get<2>(pairs[firstIndex]) - / std::get<2>(pairs[pairs.size() - 1]); - tempThreshold *= 100; - importantPairs_ = std::min(importantPairs_, tempThreshold); - } - } + auto fixImportantPairsThreshold + = [&importantPairsOriginal, this](FTMTree_MT *tree) { + double importantPairs = importantPairsOriginal; + if(MaximumImportantPairs > 0 or MinimumImportantPairs > 0) { + std::vector> pairs; + tree->getPersistencePairsFromTree(pairs, false); + if(MaximumImportantPairs > 0) { + int firstIndex = pairs.size() - MaximumImportantPairs; + firstIndex + = std::max(std::min(firstIndex, int(pairs.size()) - 1), 0); + double tempThreshold = 0.999 * std::get<2>(pairs[firstIndex]) + / std::get<2>(pairs[pairs.size() - 1]); + tempThreshold *= 100; + importantPairs = std::max(importantPairs, tempThreshold); + } + if(MinimumImportantPairs > 0) { + int firstIndex = pairs.size() - MinimumImportantPairs; + firstIndex + = std::max(std::min(firstIndex, int(pairs.size()) - 1), 0); + double tempThreshold = 0.999 * std::get<2>(pairs[firstIndex]) + / std::get<2>(pairs[pairs.size() - 1]); + tempThreshold *= 100; + importantPairs = std::min(importantPairs, tempThreshold); + } + } + return importantPairs; + }; + importantPairs_ = fixImportantPairsThreshold(trees[i]); // Get is interpolated tree (temporal subsampling) bool isInterpolatedTree = false; @@ -870,10 +997,45 @@ class ttkMergeTreeVisualization : public ttk::MergeTreeVisualization { // Get branching printMsg("// Get branching", ttk::debug::Priority::VERBOSE); - std::vector treeBranching; - std::vector treeBranchingID; - if(not isPersistenceDiagram) + std::vector treeBranching, treePathing; + std::vector treeBranchingID, treePathingID; + std::vector isRootPath; + std::vector pathOrigin; + if(not isPersistenceDiagram) { trees[i]->getTreeBranching(treeBranching, treeBranchingID); + if(pathPlanarLayout_ and !pathMatchings.empty() + and !pathMatchings[c].empty()) { + isRootPath.resize(trees[i]->getNumberOfNodes()); + std::fill(isRootPath.begin(), isRootPath.end(), false); + pathOrigin.resize(trees[i]->getNumberOfNodes()); + if(ShiftMode == 1) + getTreePathing( + trees[i], pathMatchings[c], true, treePathing, treePathingID); + else + getTreePathing(trees[i], pathMatchings[c][i], false, treePathing, + treePathingID); + bool isFirstTree = (ShiftMode == 1); + std::vector nodeLevel; + trees[i]->getAllNodeLevel(nodeLevel); + for(unsigned int j = 0; j < pathMatchings[c].size(); ++j) { + if((int)j != i and ShiftMode != 1) + continue; + for(auto &match : pathMatchings[c][j]) { + auto first + = (isFirstTree ? match.first.first : match.second.first); + auto second + = (isFirstTree ? match.first.second : match.second.second); + auto lowest + = (nodeLevel[first] < nodeLevel[second] ? second : first); + auto highest + = (nodeLevel[first] < nodeLevel[second] ? first : second); + isRootPath[highest] = true; + pathOrigin[lowest] = highest; + pathOrigin[highest] = lowest; + } + } + } + } // Get shift printMsg("// Get shift", ttk::debug::Priority::VERBOSE); @@ -911,22 +1073,52 @@ class ttkMergeTreeVisualization : public ttk::MergeTreeVisualization { break; } - // Get dimension shift - printMsg("// Get dimension shift", ttk::debug::Priority::VERBOSE); - double diff_z = PlanarLayout ? 0 : -std::get<4>(allBounds[i]); - // TODO DimensionToShift for Planar Layout - if(not PlanarLayout) - if(DimensionToShift != 0) { // is not X - if(DimensionToShift == 2) // is Z - diff_z = diff_x; - else if(DimensionToShift == 1) // is Y - diff_y = diff_x; - diff_x = -std::get<0>(allBounds[i]); - } + // isImportantPairVector + auto getIsImportantPairVector + = [this](FTMTree_MT *tree, std::vector &isImportantPairVector, + double importantPairs) { + isImportantPairVector.resize(tree->getNumberOfNodes()); + for(unsigned int n = 0; n < isImportantPairVector.size(); ++n) + isImportantPairVector[n] = tree->isImportantPair( + n, importantPairs, excludeImportantPairsLowerValues_, + excludeImportantPairsHigherValues_); + }; + std::vector isImportantPairVector; + getIsImportantPairVector( + trees[i], isImportantPairVector, importantPairs_); + + // Layout correspondence function + auto getLayoutCorr + = [](FTMTree_MT *tree, std::vector &layoutCorr) { + layoutCorr.resize(tree->getNumberOfNodes()); + int cptNode = 0; + std::queue queueLayoutCorr; + queueLayoutCorr.emplace(tree->getRoot()); + while(!queueLayoutCorr.empty()) { + idNode node = queueLayoutCorr.front(); + queueLayoutCorr.pop(); + + // Push children to the queue + std::vector children; + tree->getChildren(node, children); + for(auto child : children) + queueLayoutCorr.emplace(child); + + layoutCorr[node] = cptNode; + cptNode += 2; + } + }; // Planar layout printMsg("// Planar Layout", ttk::debug::Priority::VERBOSE); + std::vector layoutCorr; + getLayoutCorr(trees[i], layoutCorr); std::vector layout; + // TODO remove baryMatchingVector if it is not used + std::vector baryMatchingVector; + // TODO put this declaration just before it is used if the vector is + // not needed elsewhere + std::vector isImportantPairBaryVector; if(PlanarLayout) { double refPersistence; if(clusteringOutput) @@ -935,21 +1127,116 @@ class ttkMergeTreeVisualization : public ttk::MergeTreeVisualization { else refPersistence = trees[0]->getNodePersistence(trees[0]->getRoot()); - if(not isPersistenceDiagram) + if(not isPersistenceDiagram) { treePlanarLayout( trees[i], allBaryBounds[c], refPersistence, layout); - else { + // Planar Layout alignment given barycenter + if(alignTrees and ShiftMode != 1) { + // Create barycenter layout + std::vector layoutBary; + treePlanarLayout( + barycenters[0], allBaryBounds[c], refPersistence, layoutBary); + std::vector layoutBaryCorr; + getLayoutCorr(barycenters[0], layoutBaryCorr); + // Get barycenter important pairs bool vector + double isImportantPairBary + = fixImportantPairsThreshold(barycenters[0]); + getIsImportantPairVector( + barycenters[0], isImportantPairBaryVector, isImportantPairBary); + baryMatchingVector.resize(barycenters[0]->getNumberOfNodes(), -1); + for(auto match : outputMatchingBarycenter[0][i]) + baryMatchingVector[std::get<0>(match)] = std::get<1>(match); + std::vector shifts(trees[i]->getNumberOfNodes(), 0); + for(auto match : outputMatchingBarycenter[0][i]) { + // Update tree important pair according barycenter + isImportantPairVector[std::get<1>(match)] + = isImportantPairBaryVector[std::get<0>(match)]; + // Update tree layout according barycenter layout + layout[layoutCorr[std::get<1>(match)]] + = layoutBary[layoutBaryCorr[std::get<0>(match)]]; + if(not isImportantPairVector[std::get<1>(match)]) { + // Search for birth swap with an important pair + ttk::ftm::idNode node = std::get<0>(match); + while(not isImportantPairBaryVector[node]) + node = barycenters[0]->getParentSafe(node); + bool baryNodeSup + = barycenters[0]->getValue(node) + > barycenters[0]->getValue(std::get<0>(match)); + bool treeNodeSup + = trees[i]->getValue(baryMatchingVector[node]) + > trees[i]->getValue(std::get<1>(match)); + if((not baryNodeSup and treeNodeSup) + or (baryNodeSup and not treeNodeSup)) { + float shift + = std::abs(layout[layoutCorr[std::get<1>(match)]] + - layoutBary[layoutBaryCorr[node]]); + layout[layoutCorr[std::get<1>(match)]] + = layoutBary[layoutBaryCorr[node]]; + std::queue queueShift; + queueShift.emplace( + trees[i]->getNode(std::get<1>(match))->getOrigin()); + while(!queueShift.empty()) { + ttk::ftm::idNode nodeToShift = queueShift.front(); + queueShift.pop(); + shifts[nodeToShift] += shift; + ttk::ftm::idNode nodeToShiftParent + = trees[i]->getParentSafe(nodeToShift); + if(nodeToShiftParent != std::get<1>(match)) + queueShift.emplace(nodeToShiftParent); + std::vector children; + trees[i]->getChildren(nodeToShift, children); + for(auto &child : children) + queueShift.emplace(child); + } + } + } + } + for(unsigned int s = 0; s < shifts.size(); ++s) + layout[layoutCorr[s]] += shifts[s]; + } + } else { persistenceDiagramPlanarLayout(trees[i], layout); } } + // Get dimension shift + printMsg("// Get dimension shift", ttk::debug::Priority::VERBOSE); + double diff_z = PlanarLayout ? 0 : -std::get<4>(allBounds[i]); + if(DimensionToShift != 0) { // is not X + float minX = 0; + if(PlanarLayout) { + minX = layout[0]; + for(unsigned int l = 0; l < layout.size(); ++l) { + if(l % 2 == 0) + minX = std::min(minX, layout[l]); + } + } + double new_diff_x = PlanarLayout ? -minX : -std::get<0>(allBounds[i]); + bool diffYAllowed + = (not clusteringOutput + or (trees.size() == 2 and barycenters.size() == 1)); + if(DimensionToShift == 2) { + // is Z + diff_z = -diff_x; + diff_x = new_diff_x; + } else if(diffYAllowed and DimensionToShift == 1) { + // is Y + diff_y = diff_x; + diff_x = new_diff_x; + } else if(DimensionToShift == 3) { + // Custom + if(diffYAllowed) + diff_y = YShift * diff_x + (1 - YShift) * diff_y; + diff_z = ZShift * -diff_x + (1 - ZShift) * diff_z; + diff_x = XShift * diff_x + (1 - XShift) * new_diff_x; + } + } + // Internal arrays printMsg("// Internal arrays", ttk::debug::Priority::VERBOSE); - int cptNode = 0; nodeCorr[i].resize(trees[i]->getNumberOfNodes()); std::vector treeSimplexId(trees[i]->getNumberOfNodes()); std::vector treeDummySimplexId(trees[i]->getNumberOfNodes()); - std::vector layoutCorr(trees[i]->getNumberOfNodes()); std::vector treeMatching(trees[i]->getNumberOfNodes(), -1); if(clusteringOutput and ShiftMode != 1) for(auto match : outputMatchingBarycenter[c][i]) @@ -980,6 +1267,7 @@ class ttkMergeTreeVisualization : public ttk::MergeTreeVisualization { idNode const node = queue.front(); queue.pop(); idNode const nodeOrigin = trees[i]->getNode(node)->getOrigin(); + idNode const nodeParent = trees[i]->getParentSafe(node); // Push children to the queue printMsg( @@ -1034,11 +1322,9 @@ class ttkMergeTreeVisualization : public ttk::MergeTreeVisualization { getPoint(treesNodes[i], nodeMesh, point); } if(PlanarLayout) { - layoutCorr[node] = cptNode; - point[0] = layout[cptNode]; - point[1] = layout[cptNode + 1]; + point[0] = layout[layoutCorr[node]]; + point[1] = layout[layoutCorr[node] + 1]; point[2] = 0; - cptNode += 2; } point[0] += diff_x; point[1] += diff_y; @@ -1059,10 +1345,10 @@ class ttkMergeTreeVisualization : public ttk::MergeTreeVisualization { prevXMax = 0; } - // TODO too many dummy nodes are created bool dummyNode = PlanarLayout and not branchDecompositionPlanarLayout_ - and (!trees[i]->isRoot(node) or isPersistenceDiagram); + and ((!trees[i]->isRoot(node) and !trees[i]->isLeaf(node)) + or isPersistenceDiagram); dummyNode = dummyNode or embeddedDiagram; if(dummyNode) { double pointToAdd[3] = {0, 0, 0}; @@ -1104,7 +1390,7 @@ class ttkMergeTreeVisualization : public ttk::MergeTreeVisualization { SimplexId const nextPointId = points->InsertNextPoint(point); treeSimplexId[node] = nextPointId; nodeCorr[i][node] = nextPointId; - if(dummyNode) + if(dummyNode and not pathPlanarLayout_) nodeCorr[i][node] = treeDummySimplexId[node]; if(isPersistenceDiagram) nodeCorr[i][node] = nextPointId; @@ -1115,6 +1401,9 @@ class ttkMergeTreeVisualization : public ttk::MergeTreeVisualization { ? node : treeBranching[node]); + // Path Layout Dummy Node + bool pathDummyNode = false; + // -------------- // Insert cell connecting parent // -------------- @@ -1124,7 +1413,6 @@ class ttkMergeTreeVisualization : public ttk::MergeTreeVisualization { vtkIdType pointIds[2]; pointIds[0] = treeSimplexId[node]; - idNode const nodeParent = trees[i]->getParentSafe(node); // TODO too many dummy cells are created bool const dummyCell = PlanarLayout and not branchDecompositionPlanarLayout_ @@ -1149,12 +1437,36 @@ class ttkMergeTreeVisualization : public ttk::MergeTreeVisualization { } else pointIds[1] = treeSimplexId[nodeParent]; + // Path Layout Dummy Cell + bool isNodeParentImportant = isImportantPairVector[nodeParent]; + bool pathDummyCell = not dummyCell and pathPlanarLayout_ + and isNodeParentImportant + and !trees[i]->isRoot(nodeParent); + if(not pathDummyCell and alignTrees and ShiftMode != 1) { + pathDummyCell + = not dummyCell and pathPlanarLayout_ + and layout[layoutCorr[node]] != layout[layoutCorr[nodeParent]] + and !trees[i]->isRoot(nodeParent); + } + if(pathDummyCell) { + pathDummyNode = true; + double pathDummyPoint[3] + = {layout[layoutCorr[node]] + diff_x, + layout[layoutCorr[nodeParent] + 1] + diff_y, 0. + diff_z}; + SimplexId pathDummyPointId + = points->InsertNextPoint(pathDummyPoint); + vtkIdType pathDummyCellPointIds[2]; + pathDummyCellPointIds[0] = pathDummyPointId; + pathDummyCellPointIds[1] = treeSimplexId[nodeParent]; + vtkArcs->InsertNextCell(VTK_LINE, 2, pathDummyCellPointIds); + pointIds[1] = pathDummyPointId; + } vtkArcs->InsertNextCell(VTK_LINE, 2, pointIds); // -------------- // Arc field // -------------- - int const toAdd = (dummyCell ? 2 : 1); + int const toAdd = 1 + (dummyCell ? 1 : 0) + (pathDummyCell ? 1 : 0); for(int toAddT = 0; toAddT < toAdd; ++toAddT) { // Add arc matching percentage if(ShiftMode == 1) { // Star Barycenter @@ -1164,21 +1476,38 @@ class ttkMergeTreeVisualization : public ttk::MergeTreeVisualization { allBaryPercentMatch[c][nodeToGet]); } - // Add branch bary ID + // Add branch/path bary ID printMsg( "// Push arc bary branch id", ttk::debug::Priority::VERBOSE); if(clusteringOutput and ShiftMode != 1) { + // Branch int tBranchID = -1; auto nodeToGet = node; if(treeMatching[nodeToGet] < allBaryBranchingID[c].size()) tBranchID = allBaryBranchingID[c][treeMatching[nodeToGet]]; branchBaryID->InsertNextTuple1(tBranchID); + // Path + if(pathPlanarLayout_ and !pathMatchings.empty() + and !pathMatchings[c].empty()) { + int tPathID = -1; + nodeToGet = node; + if(treeMatching[nodeToGet] < allBaryPathingID[c].size()) + tPathID = allBaryPathingID[c][treeMatching[nodeToGet]]; + pathBaryID->InsertNextTuple1(tPathID); + } } - // Add branch ID + // Add branch/path ID if(not isPersistenceDiagram) { + // Branch int const tBranchID = treeBranchingID[node]; branchID->InsertNextTuple1(tBranchID); + // Path + if(pathPlanarLayout_ and !pathMatchings.empty() + and !pathMatchings[c].empty()) { + int tPathID = treePathingID[node]; + pathID->InsertNextTuple1(tPathID); + } } // Add up and down nodeId @@ -1229,7 +1558,7 @@ class ttkMergeTreeVisualization : public ttk::MergeTreeVisualization { isImportantPairsArc->InsertNextTuple1(isImportant); // Add isDummyArc - bool const isDummy = toAdd == 2 and toAddT == 0; + bool const isDummy = toAdd >= 2 and toAddT == (toAdd - 2); isDummyArc->InsertNextTuple1(isDummy); // Add isInterpolatedTree @@ -1273,13 +1602,17 @@ class ttkMergeTreeVisualization : public ttk::MergeTreeVisualization { // -------------- // Node field // -------------- - int const toAdd = (dummyNode ? 2 : 1); + int const toAdd = 1 + (dummyNode ? 1 : 0) + (pathDummyNode ? 1 : 0); for(int toAddT = 0; toAddT < toAdd; ++toAddT) { + bool isPathDummyNode = pathDummyNode and toAdd >= 2 + and toAddT == (toAdd - 1) + and !trees[i]->isRoot(node); + auto nodeToGet = (isPathDummyNode ? nodeParent : node); // Add node id - nodeID->InsertNextTuple1(treeSimplexId[node]); + nodeID->InsertNextTuple1(treeSimplexId[nodeToGet]); // Add trueNodeId - trueNodeID->InsertNextTuple1(node); + trueNodeID->InsertNextTuple1(nodeToGet); // Add VertexId int nodeVertexId = -1; @@ -1293,7 +1626,8 @@ class ttkMergeTreeVisualization : public ttk::MergeTreeVisualization { vertexID->InsertNextTuple1(nodeVertexId); // Add node scalar - scalar->InsertNextTuple1(trees[i]->getValue(node)); + auto scalarValue = trees[i]->getValue(nodeToGet); + scalar->InsertNextTuple1(scalarValue); // Add criticalType printMsg("// Add criticalType", ttk::debug::Priority::VERBOSE); @@ -1339,30 +1673,56 @@ class ttkMergeTreeVisualization : public ttk::MergeTreeVisualization { criticalType->InsertNextTuple1(criticalTypeT); // Add node matching percentage - if(ShiftMode == 1) { // Star Barycenter + if(ShiftMode == 1) // Star Barycenter percentMatch->InsertNextTuple1(allBaryPercentMatch[c][node]); - } - // Add node branch bary id + // Add node branch/path bary id printMsg( "// Add node bary branch id", ttk::debug::Priority::VERBOSE); if(clusteringOutput and ShiftMode != 1) { + // Branch int tBranchID = -1; - if(treeMatching[node] < allBaryBranchingID[c].size()) { - tBranchID = allBaryBranchingID[c][treeMatching[node]]; - if(!trees[i]->isLeaf(node) - && treeMatching[nodeOrigin] < allBaryBranchingID[c].size()) - tBranchID = allBaryBranchingID[c][treeMatching[nodeOrigin]]; - } + auto branchNode + = (isPathDummyNode + ? trees[i]->getNode(treeBranching[node])->getOrigin() + : (trees[i]->isLeaf(node) ? node : nodeOrigin)); + if(treeMatching[branchNode] < allBaryBranchingID[c].size()) + tBranchID = allBaryBranchingID[c][treeMatching[branchNode]]; branchBaryNodeID->InsertNextTuple1(tBranchID); + // Path + if(pathPlanarLayout_ and !pathMatchings.empty() + and !pathMatchings[c].empty()) { + int tPathID = -1; + auto pathNode + = (isPathDummyNode + ? node + : (not isRootPath[node] ? node : pathOrigin[node])); + if(treeMatching[pathNode] < allBaryPathingID[c].size()) + tPathID = allBaryPathingID[c][treeMatching[pathNode]]; + pathBaryNodeID->InsertNextTuple1(tPathID); + } } - // Add node branch id + // Add node branch/path id if(not isPersistenceDiagram) { - int tBranchID = treeBranchingID[node]; - if(not trees[i]->isLeaf(node)) - tBranchID = treeBranchingID[nodeOrigin]; + // Branch + auto branchNode + = (isPathDummyNode + ? trees[i]->getNode(treeBranching[node])->getOrigin() + : (trees[i]->isLeaf(node) ? node : nodeOrigin)); + int tBranchID = treeBranchingID[branchNode]; branchNodeID->InsertNextTuple1(tBranchID); + // Path + if(pathPlanarLayout_ and !pathMatchings.empty() + and !pathMatchings[c].empty()) { + auto pathNode + = (isPathDummyNode + ? node + : (not isRootPath[node] ? node : pathOrigin[node])); + // auto pathNode = node; + int tPathID = treePathingID[pathNode]; + pathNodeID->InsertNextTuple1(tPathID); + } } // Add node persistence @@ -1392,22 +1752,24 @@ class ttkMergeTreeVisualization : public ttk::MergeTreeVisualization { treeIDNode->InsertNextTuple1(i + iSampleOffset); // Add isDummyNode - bool const isDummy + bool isDummy = toAdd == 2 and toAddT == 1 and !trees[i]->isRoot(node); + if(pathPlanarLayout_) { + isDummy = (toAdd >= 2 and dummyNode and toAddT == 0 + and !trees[i]->isRoot(node)); + isDummy = isDummy or isPathDummyNode; + } isDummyNode->InsertNextTuple1(isDummy); // Add isInterpolatedTree isInterpolatedTreeNode->InsertNextTuple1(isInterpolatedTree); // Add isImportantPair - bool isImportant = false; - isImportant = trees[i]->isImportantPair( - node, importantPairs_, excludeImportantPairsLowerValues_, - excludeImportantPairsHigherValues_); + bool isImportant = isImportantPairVector[nodeToGet]; isImportantPairsNode->InsertNextTuple1(isImportant); // Add treeNodeId - treeNodeId->InsertNextTuple1(node); + treeNodeId->InsertNextTuple1(nodeToGet); // Add treeNodeIdOrigin treeNodeIdOrigin->InsertNextTuple1(nodeOrigin); @@ -1432,13 +1794,13 @@ class ttkMergeTreeVisualization : public ttk::MergeTreeVisualization { // Add custom arrays for(unsigned int ca = 0; ca < customArrays.size(); ++ca) customArraysValues[ca].emplace_back( - std::get<1>(customArrays[ca])[node]); + std::get<1>(customArrays[ca])[nodeToGet]); for(unsigned int ca = 0; ca < customIntArrays.size(); ++ca) customIntArraysValues[ca].emplace_back( - std::get<1>(customIntArrays[ca])[node]); + std::get<1>(customIntArrays[ca])[nodeToGet]); for(unsigned int ca = 0; ca < customStringArrays.size(); ++ca) customStringArraysValues[ca].emplace_back( - std::get<1>(customStringArrays[ca])[node]); + std::get<1>(customStringArrays[ca])[nodeToGet]); pointCount++; } @@ -1550,11 +1912,17 @@ class ttkMergeTreeVisualization : public ttk::MergeTreeVisualization { vtkOutputNode->GetPointData()->AddArray(nodeID); vtkOutputNode->GetPointData()->AddArray(branchNodeID); vtkOutputNode->GetPointData()->AddArray(isDummyNode); + if(pathPlanarLayout_ and !pathMatchings.empty() + and !pathMatchings[0].empty()) + vtkOutputNode->GetPointData()->AddArray(pathNodeID); } if(not branchDecompositionPlanarLayout_ and not isPersistenceDiagram) vtkOutputNode->GetPointData()->AddArray(scalar); if(clusteringOutput and ShiftMode != 1) { vtkOutputNode->GetPointData()->AddArray(branchBaryNodeID); + if(pathPlanarLayout_ and !pathMatchings.empty() + and !pathMatchings[0].empty()) + vtkOutputNode->GetPointData()->AddArray(pathBaryNodeID); vtkOutputNode->GetPointData()->AddArray(persistenceBaryNode); vtkOutputNode->GetPointData()->AddArray(persistenceBaryOrderNode); } @@ -1589,11 +1957,17 @@ class ttkMergeTreeVisualization : public ttk::MergeTreeVisualization { if(not isPersistenceDiagram) { vtkArcs->GetCellData()->AddArray(isDummyArc); vtkArcs->GetCellData()->AddArray(branchID); + if(pathPlanarLayout_ and !pathMatchings.empty() + and !pathMatchings[0].empty()) + vtkArcs->GetCellData()->AddArray(pathID); vtkArcs->GetCellData()->AddArray(upNodeId); vtkArcs->GetCellData()->AddArray(downNodeId); } if(clusteringOutput and ShiftMode != 1) { vtkArcs->GetCellData()->AddArray(branchBaryID); + if(pathPlanarLayout_ and !pathMatchings.empty() + and !pathMatchings[0].empty()) + vtkArcs->GetCellData()->AddArray(pathBaryID); vtkArcs->GetCellData()->AddArray(persistenceBaryArc); vtkArcs->GetCellData()->AddArray(persistenceBaryOrderArc); } @@ -1667,4 +2041,4 @@ class ttkMergeTreeVisualization : public ttk::MergeTreeVisualization { return getRealBounds(treeNodes, tree, nodeCorrT); } -}; +}; \ No newline at end of file diff --git a/core/vtk/ttkPlanarGraphLayout/ttkPlanarGraphLayout.cpp b/core/vtk/ttkPlanarGraphLayout/ttkPlanarGraphLayout.cpp index 17bb7a4879..c9d867a0f4 100644 --- a/core/vtk/ttkPlanarGraphLayout/ttkPlanarGraphLayout.cpp +++ b/core/vtk/ttkPlanarGraphLayout/ttkPlanarGraphLayout.cpp @@ -139,6 +139,7 @@ int ttkPlanarGraphLayout::mergeTreePlanarLayoutCallTemplate( visuMaker.setPlanarLayout(true); visuMaker.setOutputSegmentation(false); visuMaker.setBranchDecompositionPlanarLayout(BranchDecompositionPlanarLayout); + visuMaker.setPathPlanarLayout(PathPlanarLayout); visuMaker.setBranchSpacing(BranchSpacing); visuMaker.setImportantPairs(ImportantPairs); visuMaker.setMaximumImportantPairs(MaximumImportantPairs); diff --git a/core/vtk/ttkPlanarGraphLayout/ttkPlanarGraphLayout.h b/core/vtk/ttkPlanarGraphLayout/ttkPlanarGraphLayout.h index 9c63a2a252..9792d0ede1 100644 --- a/core/vtk/ttkPlanarGraphLayout/ttkPlanarGraphLayout.h +++ b/core/vtk/ttkPlanarGraphLayout/ttkPlanarGraphLayout.h @@ -77,6 +77,7 @@ class TTKPLANARGRAPHLAYOUT_EXPORT ttkPlanarGraphLayout // --- Merge Tree Planar Layout bool InputIsAMergeTree = false; bool BranchDecompositionPlanarLayout = false; + bool PathPlanarLayout = false; double BranchSpacing = 1.; double ImportantPairs = 10.; // important pairs threshold int MaximumImportantPairs = 0; @@ -113,6 +114,9 @@ class TTKPLANARGRAPHLAYOUT_EXPORT ttkPlanarGraphLayout vtkSetMacro(BranchDecompositionPlanarLayout, bool); vtkGetMacro(BranchDecompositionPlanarLayout, bool); + vtkSetMacro(PathPlanarLayout, bool); + vtkGetMacro(PathPlanarLayout, bool); + vtkSetMacro(BranchSpacing, double); vtkGetMacro(BranchSpacing, double); diff --git a/paraview/xmls/MergeTreeClustering.xml b/paraview/xmls/MergeTreeClustering.xml index 2f5133a146..29ba954f5c 100644 --- a/paraview/xmls/MergeTreeClustering.xml +++ b/paraview/xmls/MergeTreeClustering.xml @@ -15,23 +15,42 @@ short_help="TTK MergeTreeClustering plugin that compute distance, geodesics and barycenters between merge trees."> This filter allows to compute distances, geodesics, barycenters and clusters of merge trees. -Two backends are available: +Four backends are available: -- The Wasserstein Distance: +- The Wasserstein Distance. Related publication: 'Wasserstein Distances, Geodesics and Barycenters of Merge Trees' Mathieu Pont, Jules Vidal, Julie Delon, Julien Tierny. Proc. of IEEE VIS 2021. IEEE Transactions on Visualization and Computer Graphics, 2021 -- The Edit Distance: +- The Edit Distance. Related publication: 'Edit distance between merge trees.' R. Sridharamurthy, T. B. Masood, A. Kamakshidasan, and V. Natarajan. IEEE Transactions on Visualization and Computer Graphics, 2018. -Only the first backend allows to compute geodesics, barycenters and clusters of merge trees. The second one can only compute distances. -These backends are different through 3 parameters: +- The Branch-Decomposition Edit Distance. +Related publication: +"Branch Decomposition-Independent Edit Distances for Merge Trees." +Florian Wetzels, Heike Leitte, and Christoph Garth. +Computer Graphics Forum, 2022. + +- The Path-Mapping Distance. +Related publications: +"A Deformation-based Edit Distance for Merge Trees" +Florian Wetzels, Christoph Garth. +TopoInVis 2022. +and +'Merge Tree Geodesics and Barycenters with Path Mappings' +F. Wetzels, M. Pont, J. Tierny and C. Garth. +Proc. of IEEE VIS 2023. +IEEE Transactions on Visualization and Computer Graphics, 2024 + +Only the first and last backend allows to compute geodesics, barycenters and clusters of merge trees. +The other ones can only compute distances, therefore, the "ComputeBarycenter" will always be false for these backends, whatever its old value. + +The first two backends are different through 3 parameters: Parameters | Wasserstein | Edit | --------------------------|-------------|-------| @@ -112,26 +131,32 @@ Online examples: - + + + + + - + property="KeepSubtree" + value="0" /> + mode="visibility" + property="Backend" + value="4" /> @@ -151,26 +176,231 @@ Online examples: - + + + + + - + + + + + value="4" /> + + + + + + + Number of barycenters/clusters to compute (performs a KMeans with merge trees as centroids). + + + + + + + + + + + + + + Use median update procedure instead of average. + + + + + + + + + + + + + + + + + + + + + + + + Use fixed member for initial barycenter candidate. + + + + + + + + + + + + + + + + + + + + + + + property="ComputeBarycenter" + value="1" /> + + + + + + + Member index for initial barycenter candidate. + + + + + + + + + + + + + + + + + + + + + - Number of barycenters/clusters to compute (performs a KMeans with merge trees as centroids). + Maximum number of iterations in barycenter computation. @@ -239,26 +469,32 @@ Online examples: - + + + + + - + property="KeepSubtree" + value="0" /> + mode="visibility" + property="Backend" + value="4" /> > + panel_visibility="advanced"> - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -492,6 +787,9 @@ Online examples: + + + diff --git a/paraview/xmls/MergeTreeDistanceMatrix.xml b/paraview/xmls/MergeTreeDistanceMatrix.xml index 829764f08c..f49b3121fa 100644 --- a/paraview/xmls/MergeTreeDistanceMatrix.xml +++ b/paraview/xmls/MergeTreeDistanceMatrix.xml @@ -8,6 +8,35 @@ +This filter allows to compute a distance matrix of a set of merge trees. + +Four backends are available: + +- The Wasserstein Distance. +Related publication: +'Wasserstein Distances, Geodesics and Barycenters of Merge Trees' +Mathieu Pont, Jules Vidal, Julie Delon, Julien Tierny. +Proc. of IEEE VIS 2021. +IEEE Transactions on Visualization and Computer Graphics, 2021 + +- The Edit Distance. +Related publication: +'Edit distance between merge trees.' +R. Sridharamurthy, T. B. Masood, A. Kamakshidasan, and V. Natarajan. +IEEE Transactions on Visualization and Computer Graphics, 2018. + +- The Branch-Decomposition Edit Distance. +Related publication: +"Branch Decomposition-Independent Edit Distances for Merge Trees." +Florian Wetzels, Heike Leitte, and Christoph Garth. +Computer Graphics Forum, 2022. + +- The Path-Mapping Distance. +Related publications: +"A Deformation-based Edit Distance for Merge Trees" +Florian Wetzels, Christoph Garth. +TopoInVis 2022. + Online examples: - https://topology-tool-kit.github.io/examples/mergeTreeClustering/ diff --git a/paraview/xmls/MergeTreeTemporalReduction.xml b/paraview/xmls/MergeTreeTemporalReduction.xml index d640fe8342..13a1c25497 100644 --- a/paraview/xmls/MergeTreeTemporalReduction.xml +++ b/paraview/xmls/MergeTreeTemporalReduction.xml @@ -21,6 +21,11 @@ Mathieu Pont, Jules Vidal, Julie Delon, Julien Tierny. Proc. of IEEE VIS 2021. IEEE Transactions on Visualization and Computer Graphics, 2021 +'Merge Tree Geodesics and Barycenters with Path Mappings' +F. Wetzels, M. Pont, J. Tierny and C. Garth. +Proc. of IEEE VIS 2023. +IEEE Transactions on Visualization and Computer Graphics, 2024 + Online examples: - https://topology-tool-kit.github.io/examples/mergeTreeTemporalReduction/ @@ -75,12 +80,30 @@ Online examples: + + + Use the path mapping distance instead of wasserstein distance. + + + + + + + Allows to use a custom time variable for each data in input. By default the nth input is considered to be the timestep n. @@ -121,6 +144,12 @@ Online examples: number_of_elements="1" default_values="0" panel_visibility="advanced"> + + + Use the L2 distance and L2 barycenter of the original data to compute the reduction (needs to have the Segmentation output of FTMTree). @@ -130,6 +159,7 @@ Online examples: + diff --git a/paraview/xmls/MergeTreeTemporalReductionDecoding.xml b/paraview/xmls/MergeTreeTemporalReductionDecoding.xml index 5e04f6151f..6521a8f36f 100644 --- a/paraview/xmls/MergeTreeTemporalReductionDecoding.xml +++ b/paraview/xmls/MergeTreeTemporalReductionDecoding.xml @@ -19,6 +19,11 @@ Mathieu Pont, Jules Vidal, Julie Delon, Julien Tierny. Proc. of IEEE VIS 2021. IEEE Transactions on Visualization and Computer Graphics, 2021 +'Merge Tree Geodesics and Barycenters with Path Mappings' +F. Wetzels, M. Pont, J. Tierny and C. Garth. +Proc. of IEEE VIS 2023. +IEEE Transactions on Visualization and Computer Graphics, 2024 + Online examples: - https://topology-tool-kit.github.io/examples/mergeTreeTemporalReduction/ @@ -80,6 +85,18 @@ Online examples: + + + Use the path mapping distance instead of wasserstein distance. + + + + +