3#include "../utils/AdjacencyRelation.hpp"
4#include "../utils/Assert.hpp"
5#include "../utils/Altitude.hpp"
6#include "../utils/Image.hpp"
7#include "../utils/Common.hpp"
8#include "../dataStructure/FastQueue.hpp"
9#include "BuilderMorphologicalTreeByUnionFind.hpp"
10#include "detail/MorphologicalTreeConstructionTag.hpp"
25#include <unordered_map>
26#include <unordered_set>
35enum class NodeIdSpace {
47enum class ComponentTreeKind {
58enum class MorphologicalTreeKind {
62 SELF_DUAL_RESIDUAL_TREE
128 MorphologicalTreeKind treeType_ = MorphologicalTreeKind::MAX_TREE;
131 std::optional<AdjacencyRelation> adj_;
132 std::optional<std::pair<AdjacencyRelation, AdjacencyRelation>> tosAdjacencyPolicy_;
134 bool preservesHigraNodeIdSpace_ =
false;
135 bool editSessionOpen_ =
false;
138 std::vector<NodeId> properPartOwner_;
141 std::vector<NodeId> nodeParent_;
144 std::vector<NodeId> firstChild_;
145 std::vector<NodeId> nextSibling_;
146 std::vector<NodeId> prevSibling_;
147 std::vector<NodeId> lastChild_;
148 std::vector<int> numChildrenByNode_;
151 std::vector<uint8_t> alive_;
152 std::vector<NodeId> freeNodeIds_;
155 std::vector<NodeId> properHead_;
156 std::vector<NodeId> properTail_;
157 std::vector<int> numProperPartsByNode_;
158 std::vector<NodeId> nextProperPart_;
159 std::vector<NodeId> prevProperPart_;
162 struct PrePostOrderCache {
163 std::vector<int> timePreOrder;
164 std::vector<int> timePostOrder;
170 void invalidate()
noexcept { valid =
false; }
172 mutable PrePostOrderCache prePostOrderCache_;
173 mutable std::unique_ptr<LCAEulerRMQ> lcaCache_;
177 std::size_t nodeStructureVersion_ = 0;
178 std::size_t topologyVersion_ = 0;
179 std::size_t properPartVersion_ = 0;
180 std::size_t mutationVersion_ = 0;
182 enum class ChildSplicePolicy {
184 ReplaceSourceSlotWhenDirectChild
192 const NodeId id = this->allocateSlot();
197 nodeParent_[id] = id;
206 inline NodeId allocateSlot() {
207 if (!freeNodeIds_.empty()) {
209 freeNodeIds_.pop_back();
216 numChildrenByNode_[
slotId] = 0;
220 numProperPartsByNode_[
slotId] = 0;
230 numChildrenByNode_.push_back(0);
234 numProperPartsByNode_.push_back(0);
241 inline void reserveNodes(
int expected) {
247 numChildrenByNode_.reserve(
expected);
251 numProperPartsByNode_.reserve(
expected);
265 inline void invalidateHigraNodeIdSpace()
noexcept { preservesHigraNodeIdSpace_ =
false; }
270 inline void beginEditSession() {
271 if (editSessionOpen_) {
272 throw std::logic_error(
"A MorphologicalTree edit session is already open.");
274 editSessionOpen_ =
true;
280 inline void endEditSession()
noexcept {
281 editSessionOpen_ =
false;
294 nextSibling_.clear();
295 prevSibling_.clear();
297 numChildrenByNode_.clear();
299 freeNodeIds_.clear();
302 numProperPartsByNode_.clear();
304 invalidateHigraNodeIdSpace();
306 prePostOrderCache_.timePreOrder.clear();
307 prePostOrderCache_.timePostOrder.clear();
308 invalidatePrePostOrderCache();
309 invalidateAllIterators();
315 inline void releaseSlotStorage(
NodeId slotId)
noexcept {
321 numChildrenByNode_[
slotId] = 0;
325 numProperPartsByNode_[
slotId] = 0;
326 freeNodeIds_.push_back(
slotId);
339 template<
class PixelType>
342 throw std::invalid_argument(std::string(
context) +
" requires a non-null image.");
344 if (
img->getNumRows() <= 0 ||
img->getNumCols() <= 0 ||
img->getSize() <= 0) {
345 throw std::invalid_argument(std::string(
context) +
" requires a non-empty 2D image.");
354 case ToSInterpolation::SelfDual:
356 case ToSInterpolation::Min4cMax8c:
358 case ToSInterpolation::Min8cMax4c:
361 throw std::invalid_argument(
"Unsupported tree-of-shapes interpolation.");
369 throw std::invalid_argument(std::string(
context) +
" requires a live internal NodeId.");
379 throw std::invalid_argument(std::string(
context) +
" cannot target the root node.");
386 inline void rebuildProperPartLinksFromOwnership() {
387 properHead_.assign(nodeParent_.size(),
InvalidNode);
388 properTail_.assign(nodeParent_.size(),
InvalidNode);
389 numProperPartsByNode_.assign(nodeParent_.size(), 0);
390 initializeProperPartStorage(properPartOwner_.size());
421 nextProperPart_[
static_cast<size_t>(
prev)] =
next;
427 prevProperPart_[
static_cast<size_t>(
next)] =
prev;
432 --numProperPartsByNode_[
static_cast<size_t>(
ownerSlotId)];
445 nextProperPart_[
static_cast<size_t>(
tail)] =
pixelId;
446 prevProperPart_[
static_cast<size_t>(
pixelId)] =
tail;
449 ++numProperPartsByNode_[
static_cast<size_t>(
targetSlotId)];
481 numProperPartsByNode_[
static_cast<size_t>(
sourceSlotId)] = 0;
490 invalidatePrePostOrderCache();
491 bumpNodeStructureVersion();
497 inline void invalidateAllIterators()
noexcept {
498 nodeStructureVersion_ = 0;
499 topologyVersion_ = 0;
500 properPartVersion_ = 0;
508 inline void invalidateLcaCache()
const noexcept { lcaCache_.reset(); }
513 inline void bumpNodeStructureVersion()
noexcept {
514 ++nodeStructureVersion_;
516 invalidateLcaCache();
517 invalidateHigraNodeIdSpace();
523 inline void bumpTopologyVersion()
noexcept {
526 invalidateLcaCache();
527 invalidatePrePostOrderCache();
528 invalidateHigraNodeIdSpace();
534 inline void bumpProperPartVersion()
noexcept {
535 ++properPartVersion_;
537 invalidateHigraNodeIdSpace();
544 assert(
expectedVersion == nodeStructureVersion_ &&
"Alive-node iterator invalidated by node-structure mutation.");
551 assert(
expectedVersion == topologyVersion_ &&
"Topology iterator invalidated by tree-structure mutation.");
558 assert(
expectedVersion == properPartVersion_ &&
"Proper-parts iterator invalidated by proper-part mutation.");
564 inline void invalidatePrePostOrderCache()
const noexcept { prePostOrderCache_.invalidate(); }
569 inline void recomputePrePostOrderCache()
const {
570 prePostOrderCache_.timePreOrder.assign(nodeParent_.size(), -1);
571 prePostOrderCache_.timePostOrder.assign(nodeParent_.size(), -1);
574 prePostOrderCache_.valid =
true;
579 computeIncrementalAttributes(
587 prePostOrderCache_.timePostOrder[
nodeId] =
timer++;
591 prePostOrderCache_.valid =
true;
597 inline void ensurePrePostOrderCache()
const {
598 if (!prePostOrderCache_.valid) {
599 recomputePrePostOrderCache();
606 inline const LCAEulerRMQ& ensureLcaCache()
const {
608 lcaCache_ = std::make_unique<LCAEulerRMQ>(
this);
616 template<
class PreProcessing,
class MergeProcessing,
class PostProcessing>
620 computeIncrementalAttributes(tree,
childNodeId, preProcessing, mergeProcessing, postProcessing);
650 bumpTopologyVersion();
683 bumpTopologyVersion();
758 numChildrenByNode_[
fromId] = 0;
759 bumpTopologyVersion();
775 if (numChildrenByNode_[
nodeSlot] != 0 || numProperPartsByNode_[
nodeSlot] != 0) {
784 inline NodeId allocateNode() {
785 if (freeNodeIds_.empty()) {
791 invalidatePrePostOrderCache();
792 bumpNodeStructureVersion();
803 inline NodeId createDetachedNode() {
807 invalidatePrePostOrderCache();
808 bumpNodeStructureVersion();
823 invalidatePrePostOrderCache();
905 bumpProperPartVersion();
919 bumpProperPartVersion();
933 if (rootNodeId_ !=
InvalidNode && !isFreeSlot(rootNodeId_)) {
934 nodeParent_[rootNodeId_] = rootNodeId_;
946 bumpTopologyVersion();
990 requireNonEmptyImageDomain(
imgPtr,
"MorphologicalTree component-tree construction");
993 treeType_ =
isMaxtree ? MorphologicalTreeKind::MAX_TREE : MorphologicalTreeKind::MIN_TREE;
994 numRows_ =
imgPtr->getNumRows();
995 numCols_ =
imgPtr->getNumCols();
996 adj_.emplace(numRows_, numCols_, radius);
997 tosAdjacencyPolicy_ = std::nullopt;
998 initializeEmptyStorage(
static_cast<size_t>(
imgPtr->getSize()));
1010 properPartOwner_[
p] = this->rootNodeId_ = this->makeNode(
InvalidNode);
1012 properPartOwner_[
p] = this->makeNode(properPartOwner_[
parent[
p]]);
1014 properPartOwner_[
p] = properPartOwner_[
parent[
p]];
1018 properHead_.assign(nodeParent_.size(),
InvalidNode);
1019 properTail_.assign(nodeParent_.size(),
InvalidNode);
1020 numProperPartsByNode_.assign(nodeParent_.size(), 0);
1022 rebuildProperPartLinksFromOwnership();
1023 invalidateAllIterators();
1035 requireNonEmptyImageDomain(
imgPtr,
"MorphologicalTree tree-of-shapes construction");
1037 treeType_ = MorphologicalTreeKind::TREE_OF_SHAPES;
1038 numRows_ =
imgPtr->getNumRows();
1039 numCols_ =
imgPtr->getNumCols();
1040 adj_ = std::nullopt;
1041 tosAdjacencyPolicy_ = treeOfShapesAdjacencyPolicy(
interpolation, numRows_, numCols_);
1042 initializeEmptyStorage(
static_cast<size_t>(
imgPtr->getSize()));
1055 properPartOwner_[
p] = this->rootNodeId_ = this->makeNode(
InvalidNode);
1057 properPartOwner_[
p] = this->makeNode(properPartOwner_[
parent[
p]]);
1059 properPartOwner_[
p] = properPartOwner_[
parent[
p]];
1063 properHead_.assign(nodeParent_.size(),
InvalidNode);
1064 properTail_.assign(nodeParent_.size(),
InvalidNode);
1065 numProperPartsByNode_.assign(nodeParent_.size(), 0);
1067 rebuildProperPartLinksFromOwnership();
1068 invalidateAllIterators();
1081 if (
kind == MorphologicalTreeKind::SELF_DUAL_RESIDUAL_TREE) {
1082 throw std::invalid_argument(
"Higra import does not accept SELF_DUAL_RESIDUAL_TREE.");
1084 if (!
adjacency &&
kind != MorphologicalTreeKind::TREE_OF_SHAPES) {
1085 throw std::invalid_argument(
"Higra import of max/min trees requires an explicit adjacency relation.");
1092 tosAdjacencyPolicy_ = std::nullopt;
1097 throw std::invalid_argument(
"Higra leaf count must match image domain size.");
1103 throw std::invalid_argument(
"Higra parent array must contain leaves followed by at least one internal node.");
1113 numChildrenByNode_.assign(
static_cast<size_t>(
numNodeSlots), 0);
1117 numProperPartsByNode_.assign(
static_cast<size_t>(
numNodeSlots), 0);
1118 initializeProperPartStorage(
static_cast<size_t>(
numProperParts));
1126 throw std::invalid_argument(
"Each Higra leaf must point to an internal node.");
1136 throw std::invalid_argument(
"A Higra hierarchy must encode exactly one root.");
1144 throw std::invalid_argument(
"Each Higra internal node must point to another internal node or to itself.");
1151 throw std::invalid_argument(
"A Higra hierarchy must encode exactly one root.");
1153 rebuildProperPartLinksFromOwnership();
1154 invalidatePrePostOrderCache();
1155 invalidateAllIterators();
1156 preservesHigraNodeIdSpace_ =
true;
1169 throw std::invalid_argument(
"Self-dual residual tree proper-part domain must match rows * cols.");
1175 throw std::invalid_argument(
"Native topology import requires at least one internal node.");
1178 throw std::invalid_argument(
"Native topology import requires at least one proper part.");
1181 throw std::invalid_argument(
"Native topology import requires a valid root node id.");
1184 treeType_ = MorphologicalTreeKind::SELF_DUAL_RESIDUAL_TREE;
1187 adj_ = std::nullopt;
1188 tosAdjacencyPolicy_ = std::nullopt;
1196 numChildrenByNode_.assign(
static_cast<size_t>(
numNodeSlots), 0);
1198 freeNodeIds_.clear();
1201 numProperPartsByNode_.assign(
static_cast<size_t>(
numNodeSlots), 0);
1203 initializeProperPartStorage(
static_cast<size_t>(
numProperParts));
1213 throw std::invalid_argument(
"Native topology import found a detached self-parented non-root node.");
1219 throw std::invalid_argument(
"Native topology import found a parent outside the internal-node domain.");
1222 prevSibling_[
static_cast<size_t>(
nodeId)] = lastChild_[
static_cast<size_t>(
parentId)];
1226 nextSibling_[
static_cast<size_t>(lastChild_[
static_cast<size_t>(
parentId)])] =
nodeId;
1229 ++numChildrenByNode_[
static_cast<size_t>(
parentId)];
1232 throw std::invalid_argument(
"Native topology import must encode exactly one self-parented root.");
1238 throw std::invalid_argument(
"Native topology import found a proper-part owner outside the internal-node domain.");
1242 rebuildProperPartLinksFromOwnership();
1243 invalidatePrePostOrderCache();
1244 invalidateAllIterators();
1245 preservesHigraNodeIdSpace_ =
false;
1257 cloned.rootNodeId_ = rootNodeId_;
1258 cloned.treeType_ = treeType_;
1259 cloned.numRows_ = numRows_;
1260 cloned.numCols_ = numCols_;
1262 cloned.tosAdjacencyPolicy_ = tosAdjacencyPolicy_;
1263 cloned.numNodes_ = numNodes_;
1264 cloned.preservesHigraNodeIdSpace_ = preservesHigraNodeIdSpace_;
1265 cloned.editSessionOpen_ =
false;
1266 cloned.properPartOwner_ = properPartOwner_;
1267 cloned.nodeParent_ = nodeParent_;
1268 cloned.firstChild_ = firstChild_;
1269 cloned.nextSibling_ = nextSibling_;
1270 cloned.prevSibling_ = prevSibling_;
1271 cloned.lastChild_ = lastChild_;
1272 cloned.numChildrenByNode_ = numChildrenByNode_;
1274 cloned.freeNodeIds_ = freeNodeIds_;
1275 cloned.properHead_ = properHead_;
1276 cloned.properTail_ = properTail_;
1277 cloned.numProperPartsByNode_ = numProperPartsByNode_;
1278 cloned.nextProperPart_ = nextProperPart_;
1279 cloned.prevProperPart_ = prevProperPart_;
1280 cloned.prePostOrderCache_ = prePostOrderCache_;
1281 cloned.lcaCache_.reset();
1282 cloned.nodeStructureVersion_ = nodeStructureVersion_;
1283 cloned.topologyVersion_ = topologyVersion_;
1284 cloned.properPartVersion_ = properPartVersion_;
1285 cloned.mutationVersion_ = mutationVersion_;
1290 class AliveNodeIterator;
1291 class AliveNodeRange;
1292 class ChildrenIterator;
1293 class ChildrenRange;
1294 class ProperPartsIterator;
1295 class ProperPartsRange;
1296 class ConnectedComponentIterator;
1297 class ConnectedComponentRange;
1298 class PostOrderNodeIterator;
1299 class PostOrderNodeRange;
1300 class BreadthFirstNodeIterator;
1301 class BreadthFirstNodeRange;
1302 class PathToRootIterator;
1303 class PathToRootRange;
1304 class PathBetweenNodesIterator;
1305 class PathBetweenNodesRange;
1306 class SubtreeNodeIterator;
1307 class SubtreeNodeRange;
1308 class DescendantNodeRange;
1323 throw std::logic_error(std::string(
context) +
" cannot be used after the referenced tree topology has changed.");
1346 if (!preservesHigraNodeIdSpace_) {
1347 throw std::runtime_error(
"This tree does not preserve an imported Higra node-id space.");
1360 case NodeIdSpace::MORPHOLOGICAL_TREE:
1362 case NodeIdSpace::HIGRA:
1365 throw std::runtime_error(
"Unknown NodeIdSpace.");
1437 requireAliveNode(
nodeId,
"MorphologicalTree::getNumChildren");
1438 return numChildrenByNode_[
nodeId];
1445 requireAliveNode(
nodeId,
"MorphologicalTree::getNodeNumDescendants");
1446 ensurePrePostOrderCache();
1448 return (prePostOrderCache_.timePostOrder[
localId] - prePostOrderCache_.timePreOrder[
localId] - 1) / 2;
1455 requireAliveNode(
nodeId,
"MorphologicalTree::getNodeNumSiblings");
1467 requireAliveNode(
nodeId,
"MorphologicalTree::getNodeTimePreOrder");
1468 ensurePrePostOrderCache();
1469 return prePostOrderCache_.timePreOrder[
nodeId];
1476 requireAliveNode(
nodeId,
"MorphologicalTree::getNodeTimePostOrder");
1477 ensurePrePostOrderCache();
1478 return prePostOrderCache_.timePostOrder[
nodeId];
1485 requireAliveNode(
nodeId,
"MorphologicalTree::getFirstChild");
1486 return firstChild_[
nodeId];
1493 requireAliveNode(
nodeId,
"MorphologicalTree::getNextSibling");
1494 return nextSibling_[
nodeId];
1506 requireAliveNode(
nodeId,
"MorphologicalTree::getNumProperParts");
1507 return numProperPartsByNode_[
nodeId];
1514 requireAliveNode(
parentNodeId,
"MorphologicalTree::hasChild");
1515 requireAliveNode(
childId,
"MorphologicalTree::hasChild");
1525 requireAliveNode(
nodeId,
"MorphologicalTree::getNodeParent");
1552 std::vector<NodeId>
leaves;
1557 s.push(this->rootNodeId_);
1559 while (!
s.empty()) {
1561 if (numChildrenByNode_[
id] == 0) {
1592 throw std::logic_error(std::string(
context) +
" requires a committed MorphologicalTree; an edit session is still open.");
1604 if (numNodes_ == 0) {
1636 if (!tosAdjacencyPolicy_) {
1637 throw std::runtime_error(
"Tree-of-shapes adjacency policy is not available.");
1639 return tosAdjacencyPolicy_->first.getRadius();
1646 if (!tosAdjacencyPolicy_) {
1647 throw std::runtime_error(
"Tree-of-shapes adjacency policy is not available.");
1649 return tosAdjacencyPolicy_->second.getRadius();
1656 return tosAdjacencyPolicy_ ? &tosAdjacencyPolicy_->
first :
nullptr;
1663 return tosAdjacencyPolicy_ ? &tosAdjacencyPolicy_->
second :
nullptr;
1698 if (numNodes_ <= 0) {
1699 throw std::runtime_error(
"Connected-tree validation requires at least one live node.");
1702 throw std::runtime_error(
"Connected-tree validation requires a live root.");
1704 if (nodeParent_[rootNodeId_] != rootNodeId_) {
1705 throw std::runtime_error(
"Connected-tree validation requires the root to point to itself.");
1726 throw std::runtime_error(
"Connected-tree validation found a parent chain that leaves the alive node domain.");
1729 throw std::runtime_error(
"Connected-tree validation found a cycle in the parent chain.");
1735 throw std::runtime_error(
"Connected-tree validation found an alive node with no parent.");
1739 throw std::runtime_error(
"Connected-tree validation found a detached alive node.");
1744 throw std::runtime_error(
"Connected-tree validation found an alive node whose parent is outside the alive node domain.");
1749 if (
nodeId != rootNodeId_) {
1755 throw std::runtime_error(
"Connected-tree validation found getNumNodes() out of sync with the alive node slots.");
1770 throw std::runtime_error(
"Connected-tree validation found a child list that references a non-alive node.");
1773 throw std::runtime_error(
"Connected-tree validation found a child whose parent pointer disagrees with the child list.");
1776 throw std::runtime_error(
"Connected-tree validation found broken previous-sibling links.");
1779 throw std::runtime_error(
"Connected-tree validation found a node referenced by multiple child lists.");
1785 throw std::runtime_error(
"Connected-tree validation found a cycle in a child list.");
1791 throw std::runtime_error(
"Connected-tree validation found an empty child list with a non-empty tail pointer.");
1795 throw std::runtime_error(
"Connected-tree validation found an incorrect last-child pointer.");
1798 throw std::runtime_error(
"Connected-tree validation found a child list whose tail still points to a next sibling.");
1803 throw std::runtime_error(
"Connected-tree validation found an incorrect child count cache.");
1806 throw std::runtime_error(
"Connected-tree validation found child lists out of sync with parent pointers.");
1810 if (
seenAsChild[
static_cast<size_t>(rootNodeId_)] != 0) {
1811 throw std::runtime_error(
"Connected-tree validation found the root inside a child list.");
1818 throw std::runtime_error(
"Connected-tree validation found a non-root alive node missing from the child lists.");
1825 throw std::runtime_error(
"Connected-tree validation found a proper part without an alive owner.");
1842 throw std::runtime_error(
"Connected-tree validation found an invalid proper-part id in the ownership list.");
1845 throw std::runtime_error(
"Connected-tree validation found a proper-part list that disagrees with the ownership map.");
1848 throw std::runtime_error(
"Connected-tree validation found broken previous-proper-part links.");
1851 throw std::runtime_error(
"Connected-tree validation found a proper part referenced multiple times.");
1857 throw std::runtime_error(
"Connected-tree validation found a cycle in a proper-part list.");
1863 throw std::runtime_error(
"Connected-tree validation found an empty proper-part list with a non-empty tail pointer.");
1867 throw std::runtime_error(
"Connected-tree validation found an incorrect proper-part tail pointer.");
1870 throw std::runtime_error(
"Connected-tree validation found a proper-part list whose tail still points forward.");
1875 throw std::runtime_error(
"Connected-tree validation found an incorrect direct proper-part count cache.");
1878 throw std::runtime_error(
"Connected-tree validation found proper-part lists out of sync with the ownership map.");
1884 throw std::runtime_error(
"Connected-tree validation found a proper part missing from the ownership lists.");
1896 }
catch (
const std::exception&
ex) {
1897 return {
false,
ex.what()};
1899 return {
false,
"Connected-tree validation failed with an unknown error."};
1907 requireAliveNode(
u,
"MorphologicalTree::isAncestor");
1908 requireAliveNode(
v,
"MorphologicalTree::isAncestor");
1909 ensurePrePostOrderCache();
1912 return prePostOrderCache_.timePreOrder[
slotU] <= prePostOrderCache_.timePreOrder[
slotV]
1913 && prePostOrderCache_.timePostOrder[
slotU] >= prePostOrderCache_.timePostOrder[
slotV];
1920 requireAliveNode(
u,
"MorphologicalTree::isDescendant");
1921 requireAliveNode(
v,
"MorphologicalTree::isDescendant");
1922 ensurePrePostOrderCache();
1925 return prePostOrderCache_.timePreOrder[
slotV] <= prePostOrderCache_.timePreOrder[
slotU]
1926 && prePostOrderCache_.timePostOrder[
slotV] >= prePostOrderCache_.timePostOrder[
slotU];
1967 return ensureLcaCache().findLowestCommonAncestor(
u,
v);
1981 requireAliveNode(
nodeId,
"MorphologicalTree::getChildren");
1989 requireAliveNode(
nodeId,
"MorphologicalTree::getProperParts");
2001 requireAliveNode(
nodeId,
"MorphologicalTree::getConnectedComponent");
2016 requireAliveNode(
rootNodeId,
"MorphologicalTree::getPostOrderNodes");
2031 requireAliveNode(
rootNodeId,
"MorphologicalTree::getIteratorBreadthFirstTraversal");
2039 requireAliveNode(
nodeId,
"MorphologicalTree::getPathToRootNodes");
2047 requireAliveNode(
sourceNodeId,
"MorphologicalTree::getPathBetweenNodes");
2048 requireAliveNode(
targetNodeId,
"MorphologicalTree::getPathBetweenNodes");
2056 requireAliveNode(
nodeId,
"MorphologicalTree::getNodeSubtree");
2064 requireAliveNode(
nodeId,
"MorphologicalTree::getDescendants");
2089 requireAliveNonRootNode(
nodeId,
"MorphologicalTree::pruneNode");
2092 throw std::invalid_argument(
"MorphologicalTree::pruneNode requires an attached non-root node.");
2130 requireAliveNonRootNode(
nodeId,
"MorphologicalTree::mergeNodeIntoParent");
2133 throw std::invalid_argument(
"MorphologicalTree::mergeNodeIntoParent requires an attached non-root node.");
2141 bumpProperPartVersion();
2188 std::vector<NodeId> euler_;
2189 std::vector<int> depth_;
2190 std::vector<int> firstOccurrence_;
2191 std::vector<int> log2_;
2192 std::vector<int> sparseTable_;
2193 int sparseTableStride_ = 0;
2201 if (firstOccurrence_[
static_cast<size_t>(
slotId)] == -1) {
2202 firstOccurrence_[
static_cast<size_t>(
slotId)] =
static_cast<int>(euler_.size());
2205 euler_.push_back(nodeId);
2206 depth_.push_back(currentDepth);
2208 for (NodeId childNodeId : tree_->
getChildren(nodeId)) {
2209 depthFirstTraversal(childNodeId, currentDepth + 1);
2210 euler_.push_back(nodeId);
2211 depth_.push_back(currentDepth);
2218 void buildSparseTable() {
2219 const int n =
static_cast<int>(depth_.size());
2222 sparseTable_.clear();
2223 sparseTableStride_ = 0;
2227 log2_.assign(
static_cast<size_t>(n + 1), 0);
2228 for (
int i = 2; i <= n; ++i) {
2229 log2_[
static_cast<size_t>(i)] = log2_[
static_cast<size_t>(i / 2)] + 1;
2232 sparseTableStride_ = 1;
2233 while ((1 << sparseTableStride_) <= n) {
2234 ++sparseTableStride_;
2237 sparseTable_.assign(
static_cast<size_t>(n) *
static_cast<size_t>(sparseTableStride_), 0);
2239 for (
int i = 0; i < n; ++i) {
2240 sparseTable_[sparseTableIndex(i, 0)] = i;
2243 for (
int j = 1; j < sparseTableStride_; ++j) {
2244 const int blockSize = 1 << j;
2245 const int halfBlock = blockSize >> 1;
2246 for (
int i = 0; i + blockSize <= n; ++i) {
2247 const int leftIndex = sparseTable_[sparseTableIndex(i, j - 1)];
2248 const int rightIndex = sparseTable_[sparseTableIndex(i + halfBlock, j - 1)];
2249 sparseTable_[sparseTableIndex(i, j)] =
2250 depth_[
static_cast<size_t>(leftIndex)] <= depth_[
static_cast<size_t>(rightIndex)] ? leftIndex : rightIndex;
2258 std::size_t sparseTableIndex(
int row,
int col)
const {
2259 return static_cast<std::size_t
>(row) *
static_cast<std::size_t
>(sparseTableStride_) +
2260 static_cast<std::size_t
>(col);
2266 int rmq(
int left,
int right)
const {
2267 const int length = right - left + 1;
2268 const int logLength = log2_[
static_cast<size_t>(length)];
2269 const int leftIndex = sparseTable_[sparseTableIndex(left, logLength)];
2270 const int rightIndex = sparseTable_[sparseTableIndex(right - (1 << logLength) + 1, logLength)];
2271 return depth_[
static_cast<size_t>(leftIndex)] <= depth_[
static_cast<size_t>(rightIndex)] ? leftIndex : rightIndex;
2278 explicit LCAEulerRMQ(
const MorphologicalTree* tree) : tree_(tree) {
2279 if (tree_ ==
nullptr || tree_->
getRoot() == InvalidNode) {
2283 euler_.reserve(
static_cast<size_t>(std::max(0, 2 * tree_->
getNumNodes() - 1)));
2284 depth_.reserve(
static_cast<size_t>(std::max(0, 2 * tree_->
getNumNodes() - 1)));
2287 depthFirstTraversal(tree_->
getRoot(), 0);
2294 NodeId findLowestCommonAncestor(NodeId u, NodeId v)
const {
2295 const int firstU = firstOccurrence_[
static_cast<size_t>(u)];
2296 const int firstV = firstOccurrence_[
static_cast<size_t>(v)];
2297 if (firstU < 0 || firstV < 0) {
2304 std::swap(left, right);
2306 return euler_[
static_cast<size_t>(rmq(left, right))];
2321 std::size_t expectedVersion_ = 0;
2331 T_->checkNodeIteratorVersion(expectedVersion_);
2335 if (current_ >= end_) {
2369 T_->checkNodeIteratorVersion(expectedVersion_);
2381 T_->checkNodeIteratorVersion(expectedVersion_);
2404 std::size_t expectedVersion_ = 0;
2436 std::size_t expectedVersion_ = 0;
2465 T_->checkTopologyIteratorVersion(expectedVersion_);
2467 currentLocal_ = T_->nextSibling_[currentLocal_];
2476 T_->checkTopologyIteratorVersion(expectedVersion_);
2477 return currentLocal_;
2498 std::size_t expectedVersion_ = 0;
2530 std::size_t expectedVersion_ = 0;
2559 T_->checkProperPartIteratorVersion(expectedVersion_);
2561 currentPixel_ = T_->nextProperPart_[currentPixel_];
2570 T_->checkProperPartIteratorVersion(expectedVersion_);
2571 return currentPixel_;
2592 std::size_t expectedVersion_ = 0;
2623 std::vector<NodeId> nodeStack_;
2625 std::size_t expectedTopologyVersion_ = 0;
2626 std::size_t expectedProperPartVersion_ = 0;
2631 void checkVersions()
const {
2632 T_->checkTopologyIteratorVersion(expectedTopologyVersion_);
2633 T_->checkProperPartIteratorVersion(expectedProperPartVersion_);
2645 nodeStack_.push_back(*
it);
2654 while (currentPixel_ ==
InvalidNode && !nodeStack_.empty()) {
2656 nodeStack_.pop_back();
2658 currentPixel_ = T_->properHead_[
static_cast<size_t>(
nodeId)];
2703 currentPixel_ = T_->nextProperPart_[
static_cast<size_t>(currentPixel_)];
2714 return currentPixel_;
2735 std::size_t expectedTopologyVersion_ = 0;
2736 std::size_t expectedProperPartVersion_ = 0;
2775 struct Item {
NodeId id;
bool expanded; };
2777 std::vector<Item> stack_;
2779 std::size_t expectedVersion_ = 0;
2785 T_->checkTopologyIteratorVersion(expectedVersion_);
2786 while (!stack_.empty()) {
2787 Item&
top = stack_.back();
2788 if (!
top.expanded) {
2789 top.expanded =
true;
2795 stack_.push_back({*
it,
false});
2836 T_->checkTopologyIteratorVersion(expectedVersion_);
2837 if (!stack_.empty()) {
2848 T_->checkTopologyIteratorVersion(expectedVersion_);
2870 std::size_t expectedVersion_ = 0;
2902 std::size_t expectedVersion_ = 0;
2934 T_->checkTopologyIteratorVersion(expectedVersion_);
2935 if (!queue_.empty()) {
2948 T_->checkTopologyIteratorVersion(expectedVersion_);
2949 return queue_.front();
2970 std::size_t expectedVersion_ = 0;
3002 std::size_t expectedVersion_ = 0;
3031 T_->checkTopologyIteratorVersion(expectedVersion_);
3033 if (T_->
isRoot(current_)) {
3046 T_->checkTopologyIteratorVersion(expectedVersion_);
3068 std::size_t expectedVersion_ = 0;
3099 const std::vector<NodeId>* path_ =
nullptr;
3100 std::size_t index_ = 0;
3101 std::size_t expectedVersion_ = 0;
3125 const std::vector<NodeId>*
path,
3134 T_->checkTopologyIteratorVersion(expectedVersion_);
3145 T_->checkTopologyIteratorVersion(expectedVersion_);
3146 return (*path_)[index_];
3153 return path_ ==
other.path_ && index_ ==
other.index_;
3168 std::vector<NodeId> path_;
3169 std::size_t expectedVersion_ = 0;
3199 std::vector<NodeId>
path;
3265 std::vector<NodeId> stack_;
3266 std::size_t expectedVersion_ = 0;
3298 T_->checkTopologyIteratorVersion(expectedVersion_);
3299 if (!stack_.empty()) {
3307 stack_.push_back(*
it);
3317 T_->checkTopologyIteratorVersion(expectedVersion_);
3318 return stack_.back();
3339 std::size_t expectedVersion_ = 0;
3371 std::size_t expectedVersion_ = 0;
int NodeId
Node identifier type used throughout the project.
constexpr NodeId InvalidNode
Sentinel value used to denote an invalid node identifier.
std::shared_ptr< Image< PixelType > > ImagePtr
Shared pointer alias for an image with arbitrary pixel type.
std::shared_ptr< ImageUInt8 > ImageUInt8Ptr
Shared pointer to an 8-bit unsigned image.
Two-dimensional adjacency relation with configurable radius and efficient iteration.
Iterator over live node ids in the dense internal-node domain.
AliveNodeIterator(const MorphologicalTree *tree, NodeId current, NodeId end, std::size_t expectedVersion)
Creates an iterator over [current, end) with fail-fast version checking.
AliveNodeIterator & operator++()
Advances to the next live node slot.
NodeId operator*() const
Returns the current live node id.
bool operator!=(const AliveNodeIterator &other) const
Compares iterator positions for inequality.
AliveNodeIterator()=default
Creates the end/sentinel iterator.
bool operator==(const AliveNodeIterator &other) const
Compares iterator positions.
std::ptrdiff_t difference_type
Signed distance type exposed for STL compatibility.
std::input_iterator_tag iterator_category
Standard iterator category exposed for STL compatibility.
Range wrapper for iterating over live node ids.
AliveNodeRange(const MorphologicalTree *tree, NodeId begin, NodeId end, std::size_t expectedVersion)
Creates a fail-fast live-node range over dense slots.
AliveNodeIterator begin() const
Returns an iterator positioned at the first live slot.
AliveNodeIterator end() const
Returns the sentinel iterator for the live-node range.
AliveNodeRange()=default
Creates an empty live-node range.
Breadth-first iterator over one subtree.
std::ptrdiff_t difference_type
Signed distance type exposed for STL compatibility.
bool operator==(const BreadthFirstNodeIterator &other) const
Compares breadth-first iterator exhaustion state.
bool operator!=(const BreadthFirstNodeIterator &other) const
Compares breadth-first iterator exhaustion state for inequality.
BreadthFirstNodeIterator(const MorphologicalTree *tree, NodeId rootNodeId, std::size_t expectedVersion)
Creates a breadth-first iterator rooted at rootNodeId.
BreadthFirstNodeIterator & operator++()
Advances to the next node in breadth-first order.
BreadthFirstNodeIterator()=default
Creates the end/sentinel breadth-first iterator.
NodeId operator*() const
Returns the current breadth-first node id.
std::input_iterator_tag iterator_category
Standard iterator category exposed for STL compatibility.
Range wrapper for breadth-first subtree traversal.
BreadthFirstNodeIterator end() const
Returns the breadth-first range sentinel.
BreadthFirstNodeIterator begin() const
Returns an iterator at the first breadth-first node.
BreadthFirstNodeRange()=default
Creates an empty breadth-first range.
BreadthFirstNodeRange(const MorphologicalTree *tree, NodeId rootNodeId, std::size_t expectedVersion)
Creates a breadth-first range rooted at rootNodeId.
Iterator over the direct children of one node.
ChildrenIterator()=default
Creates the end/sentinel child iterator.
ChildrenIterator(const MorphologicalTree *tree, NodeId currentLocal, std::size_t expectedVersion)
Creates a child iterator starting from a linked-list slot.
bool operator==(const ChildrenIterator &other) const
Compares child iterator positions.
NodeId operator*() const
Returns the current child node id.
std::ptrdiff_t difference_type
Signed distance type exposed for STL compatibility.
bool operator!=(const ChildrenIterator &other) const
Compares child iterator positions for inequality.
ChildrenIterator & operator++()
Advances to the next sibling in the child list.
std::forward_iterator_tag iterator_category
Standard iterator category exposed for STL compatibility.
Range wrapper for direct-child iteration.
ChildrenIterator end() const
Returns the child-range sentinel iterator.
ChildrenIterator begin() const
Returns an iterator at the first child.
ChildrenRange()=default
Creates an empty child range.
ChildrenRange(const MorphologicalTree *tree, NodeId firstLocal, std::size_t expectedVersion)
Creates a range over a linked list of direct children.
Iterator over all proper parts in one connected component.
NodeId operator*() const
Returns the current proper-part id.
ConnectedComponentIterator & operator++()
Advances to the next proper part in the connected component.
ConnectedComponentIterator(const MorphologicalTree *tree, NodeId rootNodeId, std::size_t expectedTopologyVersion, std::size_t expectedProperPartVersion)
Creates an iterator over every proper part in a rooted subtree.
bool operator==(const ConnectedComponentIterator &other) const
Compares connected-component iterator positions.
std::forward_iterator_tag iterator_category
Standard iterator category exposed for STL compatibility.
std::ptrdiff_t difference_type
Signed distance type exposed for STL compatibility.
ConnectedComponentIterator()=default
Creates the end/sentinel connected-component iterator.
bool operator!=(const ConnectedComponentIterator &other) const
Compares connected-component iterator positions for inequality.
Range wrapper for connected-component proper-part iteration.
ConnectedComponentRange()=default
Creates an empty connected-component range.
ConnectedComponentIterator begin() const
Returns an iterator at the first proper part in the component.
ConnectedComponentIterator end() const
Returns the connected-component range sentinel.
ConnectedComponentRange(const MorphologicalTree *tree, NodeId rootNodeId, std::size_t expectedTopologyVersion, std::size_t expectedProperPartVersion)
Creates a range over all proper parts in the subtree of rootNodeId.
Range wrapper over the proper descendants of one node.
DescendantNodeRange(const MorphologicalTree *tree, NodeId rootNodeId, std::size_t expectedVersion)
Creates a range over proper descendants of rootNodeId.
DescendantNodeRange()=default
Creates an empty descendant range.
SubtreeNodeIterator begin() const
Returns an iterator at the first proper descendant.
SubtreeNodeIterator end() const
Returns the descendant range sentinel.
Iterator over a materialised path between two nodes.
bool operator==(const PathBetweenNodesIterator &other) const
Compares path iterator positions.
PathBetweenNodesIterator()=default
Creates the end/sentinel path-between-nodes iterator.
std::input_iterator_tag iterator_category
Standard iterator category exposed for STL compatibility.
PathBetweenNodesIterator & operator++()
Advances to the next node in the materialised path.
bool operator!=(const PathBetweenNodesIterator &other) const
Compares path iterator positions for inequality.
std::ptrdiff_t difference_type
Signed distance type exposed for STL compatibility.
NodeId operator*() const
Returns the current node id in the materialised path.
PathBetweenNodesIterator(const MorphologicalTree *tree, const std::vector< NodeId > *path, std::size_t index, std::size_t expectedVersion)
Creates an iterator over a materialised node path.
Range wrapper for the path connecting two nodes in the same component.
PathBetweenNodesIterator begin() const
Returns an iterator at the first node in the materialised path.
PathBetweenNodesRange()=default
Creates an empty path-between-nodes range.
PathBetweenNodesIterator end() const
Returns the materialised path range sentinel.
PathBetweenNodesRange(const MorphologicalTree *tree, NodeId sourceNodeId, NodeId targetNodeId, std::size_t expectedVersion)
Materialises and owns the path between two nodes.
Iterator that walks from a node towards the root.
bool operator!=(const PathToRootIterator &other) const
Compares path-to-root iterator positions for inequality.
std::input_iterator_tag iterator_category
Standard iterator category exposed for STL compatibility.
bool operator==(const PathToRootIterator &other) const
Compares path-to-root iterator positions.
std::ptrdiff_t difference_type
Signed distance type exposed for STL compatibility.
NodeId operator*() const
Returns the current node on the rootward path.
PathToRootIterator & operator++()
Advances one step toward the root.
PathToRootIterator(const MorphologicalTree *tree, NodeId current, std::size_t expectedVersion)
Creates an iterator starting at one node and walking to the root.
PathToRootIterator()=default
Creates the end/sentinel rootward-path iterator.
Range wrapper for rootward path traversal.
PathToRootRange(const MorphologicalTree *tree, NodeId start, std::size_t expectedVersion)
Creates a range from start to the connected root.
PathToRootIterator end() const
Returns the rootward-path range sentinel.
PathToRootIterator begin() const
Returns an iterator at the path start node.
PathToRootRange()=default
Creates an empty rootward-path range.
Post-order iterator over one subtree.
bool operator==(const PostOrderNodeIterator &other) const
Compares post-order iterator positions.
PostOrderNodeIterator(const MorphologicalTree *tree, NodeId rootNodeId, std::size_t expectedVersion)
Creates a post-order iterator rooted at rootNodeId.
PostOrderNodeIterator & operator++()
Advances to the next node in post-order.
NodeId operator*() const
Returns the current post-order node id.
std::input_iterator_tag iterator_category
Standard iterator category exposed for STL compatibility.
std::ptrdiff_t difference_type
Signed distance type exposed for STL compatibility.
bool operator!=(const PostOrderNodeIterator &other) const
Compares post-order iterator positions for inequality.
PostOrderNodeIterator()=default
Creates the end/sentinel post-order iterator.
Range wrapper for post-order subtree traversal.
PostOrderNodeRange(const MorphologicalTree *tree, NodeId rootNodeId, std::size_t expectedVersion)
Creates a post-order range rooted at rootNodeId.
PostOrderNodeIterator begin() const
Returns an iterator at the first post-order node.
PostOrderNodeIterator end() const
Returns the post-order range sentinel.
PostOrderNodeRange()=default
Creates an empty post-order range.
Iterator over the direct proper parts owned by one node.
ProperPartsIterator & operator++()
Advances to the next proper part in the owner list.
NodeId operator*() const
Returns the current proper-part id.
ProperPartsIterator()=default
Creates the end/sentinel proper-part iterator.
ProperPartsIterator(const MorphologicalTree *tree, NodeId currentPixel, std::size_t expectedVersion)
Creates a proper-part iterator starting from a linked-list entry.
bool operator!=(const ProperPartsIterator &other) const
Compares proper-part iterator positions for inequality.
std::forward_iterator_tag iterator_category
Standard iterator category exposed for STL compatibility.
std::ptrdiff_t difference_type
Signed distance type exposed for STL compatibility.
bool operator==(const ProperPartsIterator &other) const
Compares proper-part iterator positions.
Range wrapper for direct proper-part iteration.
ProperPartsRange()=default
Creates an empty direct proper-part range.
ProperPartsIterator end() const
Returns the direct proper-part range sentinel.
ProperPartsRange(const MorphologicalTree *tree, NodeId firstPixel, std::size_t expectedVersion)
Creates a range over one node's direct proper-part list.
ProperPartsIterator begin() const
Returns an iterator at the first direct proper part.
Depth-first iterator over a subtree in pre-order.
SubtreeNodeIterator & operator++()
Advances to the next node in pre-order subtree traversal.
std::input_iterator_tag iterator_category
Standard iterator category exposed for STL compatibility.
bool operator!=(const SubtreeNodeIterator &other) const
Compares subtree iterator exhaustion state for inequality.
bool operator==(const SubtreeNodeIterator &other) const
Compares subtree iterator exhaustion state.
SubtreeNodeIterator(const MorphologicalTree *tree, NodeId rootNodeId, std::size_t expectedVersion)
Creates a pre-order subtree iterator rooted at rootNodeId.
NodeId operator*() const
Returns the current pre-order subtree node id.
std::ptrdiff_t difference_type
Signed distance type exposed for STL compatibility.
SubtreeNodeIterator()=default
Creates the end/sentinel subtree iterator.
Range wrapper for pre-order subtree traversal.
SubtreeNodeIterator end() const
Returns the subtree range sentinel.
SubtreeNodeRange(const MorphologicalTree *tree, NodeId rootNodeId, std::size_t expectedVersion)
Creates a pre-order range over the subtree rooted at rootNodeId.
SubtreeNodeRange()=default
Creates an empty subtree range.
SubtreeNodeIterator begin() const
Returns an iterator at the subtree root.
Mutable morphological tree built directly on proper parts and dense node ids.
int getNumTotalProperParts() const
Returns the size of the proper-part domain.
DescendantNodeRange getDescendants(NodeId nodeId) const
Returns a range over all proper descendants of nodeId.
bool isNode(NodeId nodeId) const noexcept
Tests whether nodeId belongs to the internal-node id domain.
TreeEditor edit()
Opens the only public entrypoint for staged structural mutations.
ChildrenRange getChildren(NodeId nodeId) const
Returns a fail-fast range over the direct children of nodeId.
int getNumLeafNodes() const
Counts the live nodes that currently have no children.
bool hasAdjacencyRelation() const noexcept
Tests whether an adjacency relation is attached to the tree.
bool isLeaf(NodeId nodeId) const
Tests whether nodeId has no direct children.
void requireMutationVersion(std::size_t expectedVersion, const char *context) const
Rejects stale read-only views that captured an older mutation version.
double getTreeOfShapesMinTreeAdjacencyRadius() const
Returns the auxiliary min-tree adjacency radius used by the ToS interpolation policy.
bool isStrictComparable(NodeId u, NodeId v) const
Tests whether u and v are strictly comparable in the ancestry order.
MorphologicalTree(MorphologicalTree &&) noexcept=default
Moving transfers the complete topology state.
int getNumInternalNodeSlots() const
Returns the size of the dense internal-node id domain.
NodeId getNextSibling(NodeId nodeId) const
Returns the next sibling of nodeId, or InvalidNode.
bool isAlive(NodeId nodeId) const
Tests whether a node slot currently represents a live node.
int getNumFreeNodeSlots() const
Returns the number of currently reusable node slots.
std::vector< NodeId > getLeaves() const
Returns all live leaf nodes in the current hierarchy.
void validateConnectedRootedTree() const
Validates that the current structure is one connected rooted tree.
const AdjacencyRelation * getTreeOfShapesMinTreeAdjacencyRelation() const noexcept
Returns the auxiliary min-tree adjacency relation used by the ToS interpolation policy.
MorphologicalTree & operator=(const MorphologicalTree &)=delete
Copy assignment is disabled to keep topology ownership explicit.
NodeId getHigraNodeId(NodeId nodeId) const noexcept
Returns the preserved imported Higra node id for one live tree node.
SubtreeNodeRange getNodeSubtree(NodeId nodeId) const
Returns a pre-order traversal range over the subtree of nodeId.
double getTreeOfShapesMaxTreeAdjacencyRadius() const
Returns the auxiliary max-tree adjacency radius used by the ToS interpolation policy.
bool isComparable(NodeId u, NodeId v) const
Tests whether u and v are comparable in the ancestry order.
int getNodeTimePreOrder(NodeId nodeId) const
Returns the preorder time of nodeId in the cached DFS traversal.
bool isEditing() const noexcept
Tests whether a staged edit session is currently open.
BreadthFirstNodeRange getIteratorBreadthFirstTraversal() const
Returns a breadth-first traversal range rooted at the connected root.
MorphologicalTree clone() const
Creates an independent copy of the structural tree state.
void pruneNode(NodeId nodeId)
Prunes the subtree of nodeId, moving all its support to the parent.
bool isRoot(NodeId nodeId) const
Tests whether nodeId is the current root.
int getNumHigraNodes() const
Returns the size of the preserved imported Higra node-id domain.
NodeId getLowestCommonAncestor(NodeId u, NodeId v) const
Returns the lowest common ancestor of u and v.
NodeId getRoot() const
Returns the current hierarchy root.
bool isProperPart(NodeId id) const noexcept
Tests whether id belongs to the proper-part domain.
BreadthFirstNodeRange getIteratorBreadthFirstTraversal(NodeId rootNodeId) const
Returns a breadth-first traversal range rooted at rootNodeId.
MorphologicalTree(detail::MorphologicalTreeConstructionTag, std::span< const NodeId > parent, int rows, int cols, MorphologicalTreeKind kind, std::optional< AdjacencyRelation > adjacency)
Tag-protected import from compact Higra parent representation.
int getNumProperParts(NodeId nodeId) const
Returns the number of direct proper parts owned by nodeId.
bool hasChild(NodeId parentNodeId, NodeId childId) const
Tests whether childId is a direct child of parentNodeId.
NodeId getProperPartOwner(NodeId properPartId) const
Returns the live node that directly owns properPartId.
ProperPartsRange getProperParts(NodeId nodeId) const
Returns a fail-fast range over the direct proper parts of nodeId.
const AdjacencyRelation * getTreeOfShapesMaxTreeAdjacencyRelation() const noexcept
Returns the auxiliary max-tree adjacency relation used by the ToS interpolation policy.
int getNumChildren(NodeId nodeId) const
Returns the number of direct children of nodeId.
bool isStrictAncestor(NodeId u, NodeId v) const
Tests whether u is a strict ancestor of v.
PostOrderNodeRange getPostOrderNodes(NodeId rootNodeId) const
Returns a post-order traversal range rooted at rootNodeId.
const AdjacencyRelation * getAdjacencyRelation() const noexcept
Returns the read-only adjacency relation attached to the tree, if any.
PathBetweenNodesRange getPathBetweenNodes(NodeId sourceNodeId, NodeId targetNodeId) const
Returns the path that connects sourceNodeId and targetNodeId.
bool hasTreeOfShapesAdjacencyPolicy() const noexcept
Tests whether this image-built tree of shapes carries min/max auxiliary adjacency metadata.
PathToRootRange getPathToRootNodes(NodeId nodeId) const
Returns the path from nodeId to the connected root.
std::size_t getMutationVersion() const noexcept
Returns the monotonic mutation counter used by read-only views.
PostOrderNodeRange getPostOrderNodes() const
Returns a post-order traversal range rooted at the connected root.
MorphologicalTreeKind getTreeType() const noexcept
Returns the current tree type.
int getNodeIdSpaceSize(NodeIdSpace outputSpace) const
Returns the size of the requested node-id domain.
int getNumColsOfImage() const
Returns the number of image columns in the attached 2D domain.
bool hasDetachedAliveNodes() const noexcept
Returns whether the tree currently contains alive detached nodes.
TreeValidationResult validateConnectedRootedTreeResult() const noexcept
Runs strong validation and returns the result instead of throwing.
int getNodeTimePostOrder(NodeId nodeId) const
Returns the postorder time of nodeId in the cached DFS traversal.
bool isDescendant(NodeId u, NodeId v) const
Tests whether u is a descendant of v.
bool isAncestor(NodeId u, NodeId v) const
Tests whether u is an ancestor of v.
MorphologicalTree(detail::MorphologicalTreeConstructionTag, ImageUInt8Ptr imgPtr, ToSInterpolation interpolation, int infinitySeedRow, int infinitySeedCol)
Tag-protected constructor for image-based tree-of-shapes topology.
void requireNotEditing(const char *context) const
Rejects operations that require a committed connected topology.
AliveNodeRange getAliveNodeIds() const
Returns a fail-fast range over all live node ids.
int getNodeNumDescendants(NodeId nodeId) const
Returns the number of internal descendants of nodeId.
NodeId getFirstChild(NodeId nodeId) const
Returns the first direct child of nodeId, or InvalidNode.
int getNodeNumSiblings(NodeId nodeId) const
Returns the number of siblings of nodeId.
NodeId getNodeParent(NodeId nodeId) const
Returns the direct parent of nodeId.
int getNumRowsOfImage() const
Returns the number of image rows in the attached 2D domain.
int getNumNodes() const noexcept
Returns the number of currently live nodes.
AdjacencyRelation * getAdjacencyRelation() noexcept
Returns the mutable adjacency relation attached to the tree, if any.
ConnectedComponentRange getConnectedComponent(NodeId nodeId) const
Returns a fail-fast range over all proper parts in the connected component represented by nodeId.
void mergeNodeIntoParent(NodeId nodeId)
Merges nodeId into its parent and releases the emptied slot.
MorphologicalTree(detail::MorphologicalTreeConstructionTag, std::span< const NodeId > nodeParent, std::span< const NodeId > properPartOwner, NodeId root, int rows, int cols)
Tag-protected import from native MAF topology buffers.
bool isStrictDescendant(NodeId u, NodeId v) const
Tests whether u is a strict descendant of v.
MorphologicalTree(const MorphologicalTree &)=delete
Copying is disabled to keep topology ownership explicit.
Thin edit-session facade for multi-step topology updates.
Value types accepted by generic altitude helpers.
Owning result for one computed scalar attribute layout and buffer.
std::vector< Real > second
Flat per-node attribute buffer indexed through first.
AttributeNames first
Layout used to interpret second; kept public for tuple-like access.
Non-throwing validation result returned by edit-session checks.
std::string message
Human-readable diagnostic, especially useful when ok is false.