3#include "../utils/RegularGridAdjacency2D.hpp"
4#include "../utils/Assert.hpp"
5#include "../utils/Altitude.hpp"
6#include "../utils/Common.hpp"
7#include "../utils/Contract.hpp"
8#include "../dataStructure/FastQueue.hpp"
10#include "ProperPartDomain.hpp"
11#include "detail/MorphologicalTreeConstructionTag.hpp"
12#include "detail/NativeHierarchyValidationDetail.hpp"
27#include <unordered_map>
28#include <unordered_set>
35class CommittedTreeAccess;
41enum class NodeIdSpace { MorphologicalTree, Higra };
76enum class TreeEditValidationMode { Complete, Incremental };
118 friend class detail::CommittedTreeAccess;
128 std::optional<GridDomain2D> gridDomain2D_;
132 std::optional<NodeId> preservedExternalNodeIdOffset_;
134 bool editSessionOpen_ =
false;
140 std::vector<NodeId> smallestNodeMap_;
144 std::vector<NodeId> nodeParent_;
148 std::vector<NodeId> firstChild_;
150 std::vector<NodeId> nextSibling_;
152 std::vector<NodeId> prevSibling_;
154 std::vector<NodeId> lastChild_;
156 std::vector<int> numChildrenByNode_;
160 std::vector<uint8_t> alive_;
162 std::vector<NodeId> freeNodeIds_;
166 std::vector<PixelId> properHead_;
168 std::vector<PixelId> properTail_;
170 std::vector<int> properPartCardinalityByNode_;
172 std::vector<PixelId> nextProperPart_;
174 std::vector<PixelId> prevProperPart_;
178 struct DfsIntervalCache {
180 std::vector<int> entryIndex;
182 std::vector<int> exitIndex;
186 void invalidate()
noexcept { valid =
false; }
189 mutable DfsIntervalCache dfsIntervalCache_;
191 mutable std::unique_ptr<LCAEulerRMQ> lcaCache_;
194 struct NodeSupportMetadataCache {
196 std::vector<std::int32_t> cardinalityByNode;
198 std::vector<PixelId> smallestPixelByNode;
200 std::size_t nodeStructureVersion = std::numeric_limits<std::size_t>::max();
202 std::size_t topologyVersion = std::numeric_limits<std::size_t>::max();
204 std::size_t properPartVersion = std::numeric_limits<std::size_t>::max();
207 void invalidate()
noexcept {
208 nodeStructureVersion = std::numeric_limits<std::size_t>::max();
209 topologyVersion = std::numeric_limits<std::size_t>::max();
210 properPartVersion = std::numeric_limits<std::size_t>::max();
214 mutable NodeSupportMetadataCache nodeSupportMetadataCache_;
218 std::size_t nodeStructureVersion_ = 0;
220 std::size_t topologyVersion_ = 0;
222 std::size_t properPartVersion_ = 0;
224 std::size_t mutationVersion_ = 0;
227 enum class ChildSplicePolicy { AppendToTargetTail, ReplaceSourceSlotWhenDirectChild };
235 inline NodeId allocateSlot() {
236 if (!freeNodeIds_.empty()) {
238 freeNodeIds_.pop_back();
245 numChildrenByNode_[
slotId] = 0;
249 properPartCardinalityByNode_[
slotId] = 0;
259 numChildrenByNode_.push_back(0);
263 properPartCardinalityByNode_.push_back(0);
272 inline void initializeProperPartStorage(
size_t numPixels) {
280 inline void invalidateHigraNodeIdSpace()
noexcept { preservedExternalNodeIdOffset_.reset(); }
293 throw std::invalid_argument(
"An external internal-node offset must be non-negative.");
301 inline void beginEditSession() {
302 if (editSessionOpen_) {
303 throw std::logic_error(
"A MorphologicalTree edit session is already open.");
305 editSessionOpen_ =
true;
311 inline void endEditSession()
noexcept { editSessionOpen_ =
false; }
318 inline void recordEditCommit(TreeEditValidationMode mode)
noexcept {
319 if (mode == TreeEditValidationMode::Incremental) {
331 inline void initializeEmptyStorage(
size_t numPixels) {
338 nextSibling_.clear();
339 prevSibling_.clear();
341 numChildrenByNode_.clear();
343 freeNodeIds_.clear();
346 properPartCardinalityByNode_.clear();
348 invalidateHigraNodeIdSpace();
350 dfsIntervalCache_.entryIndex.clear();
351 dfsIntervalCache_.exitIndex.clear();
352 invalidateDfsIntervalCache();
353 nodeSupportMetadataCache_.cardinalityByNode.clear();
354 nodeSupportMetadataCache_.smallestPixelByNode.clear();
355 nodeSupportMetadataCache_.invalidate();
356 invalidateAllIterators();
368 freeNodeIds_.push_back(
slotId);
374 numChildrenByNode_[
slotId] = 0;
378 properPartCardinalityByNode_[
slotId] = 0;
413 inline void rebuildProperPartLinksFromSmallestNodeMap() {
416 properPartCardinalityByNode_.assign(nodeParent_.size(), 0);
417 initializeProperPartStorage(smallestNodeMap_.size());
445 const PixelId prev = prevProperPart_[
static_cast<size_t>(pixel)];
446 const PixelId next = nextProperPart_[
static_cast<size_t>(pixel)];
451 nextProperPart_[
static_cast<size_t>(
prev)] = next;
457 prevProperPart_[
static_cast<size_t>(next)] =
prev;
460 nextProperPart_[
static_cast<size_t>(pixel)] =
InvalidPixel;
461 prevProperPart_[
static_cast<size_t>(pixel)] =
InvalidPixel;
472 smallestNodeMap_[
static_cast<size_t>(pixel)] =
targetSlotId;
478 nextProperPart_[
static_cast<size_t>(
tail)] = pixel;
479 prevProperPart_[
static_cast<size_t>(pixel)] =
tail;
482 ++properPartCardinalityByNode_[
static_cast<size_t>(
targetSlotId)];
498 smallestNodeMap_[
static_cast<size_t>(pixel)] =
targetSlotId;
517 properPartCardinalityByNode_[
static_cast<size_t>(
sourceSlotId)] = 0;
528 invalidateDfsIntervalCache();
529 bumpNodeStructureVersion();
535 inline void invalidateAllIterators()
noexcept {
536 nodeStructureVersion_ = 0;
537 topologyVersion_ = 0;
538 properPartVersion_ = 0;
541 nodeSupportMetadataCache_.invalidate();
547 inline void invalidateLcaCache()
const noexcept { lcaCache_.reset(); }
552 inline void bumpNodeStructureVersion()
noexcept {
553 ++nodeStructureVersion_;
555 invalidateLcaCache();
556 invalidateHigraNodeIdSpace();
562 inline void bumpTopologyVersion()
noexcept {
565 invalidateLcaCache();
566 invalidateDfsIntervalCache();
567 invalidateHigraNodeIdSpace();
573 inline void bumpProperPartVersion()
noexcept {
574 ++properPartVersion_;
576 invalidateHigraNodeIdSpace();
585 assert(
expectedVersion == nodeStructureVersion_ &&
"Alive-node iterator invalidated by node-structure mutation.");
594 assert(
expectedVersion == topologyVersion_ &&
"Topology iterator invalidated by tree-structure mutation.");
603 assert(
expectedVersion == properPartVersion_ &&
"Proper-parts iterator invalidated by proper-part mutation.");
609 inline void invalidateDfsIntervalCache()
const noexcept { dfsIntervalCache_.invalidate(); }
614 inline void recomputeDfsIntervalCache()
const {
615 dfsIntervalCache_.entryIndex.assign(nodeParent_.size(), -1);
616 dfsIntervalCache_.exitIndex.assign(nodeParent_.size(), -1);
619 dfsIntervalCache_.valid =
true;
625 std::vector<std::pair<NodeId, NodeId>>
stack;
626 stack.emplace_back(rootNodeId_, firstChild_[rootNodeId_]);
628 while (!
stack.empty()) {
641 dfsIntervalCache_.valid =
true;
647 inline void ensureDfsIntervalCache()
const {
648 if (!dfsIntervalCache_.valid) {
649 recomputeDfsIntervalCache();
658 inline const LCAEulerRMQ& ensureLcaCache()
const {
660 lcaCache_ = std::make_unique<LCAEulerRMQ>(
this);
672 [[
nodiscard]]
inline std::optional<NodeId> lowestCommonAncestorFromDfsIntervalsEstablished(
NodeId u,
NodeId v)
const {
676 ensureDfsIntervalCache();
677 if (dfsIntervalCache_.entryIndex[
static_cast<std::size_t
>(
u)] < 0 ||
678 dfsIntervalCache_.entryIndex[
static_cast<std::size_t
>(
v)] < 0) {
682 return dfsIntervalCache_.entryIndex[
static_cast<std::size_t
>(
ancestor)] <=
683 dfsIntervalCache_.entryIndex[
static_cast<std::size_t
>(node)] &&
684 dfsIntervalCache_.exitIndex[
static_cast<std::size_t
>(
ancestor)] >=
685 dfsIntervalCache_.exitIndex[
static_cast<std::size_t
>(node)];
703 const std::optional<NodeId>
intervalResult = lowestCommonAncestorFromDfsIntervalsEstablished(
u,
v);
711 [[
nodiscard]]
inline long double estimatedLcaRmqStorageBytes()
const noexcept {
713 const std::size_t
eulerLength = numNodes_ > 0 ?
static_cast<std::size_t
>(numNodes_) * 2 - 1 : 0;
718 return static_cast<long double>(nodeParent_.size()) *
static_cast<long double>(36 + 8 *
rmqLevels);
733 template <
typename QueryAccessor,
typename ResultConsumer>
748 const std::size_t
numSlots = nodeParent_.size();
754 const std::optional<NodeId>
intervalResult = lowestCommonAncestorFromDfsIntervalsEstablished(first, second);
773 if (lowestCommonAncestorFromDfsIntervalsEstablished(first, second).
has_value()) {
785 if (!
forceTarjan && lowestCommonAncestorFromDfsIntervalsEstablished(first, second).
has_value()) {
791 for (std::size_t index = 1; index <
queryOffsets.size(); ++index) {
802 if (!
forceTarjan && lowestCommonAncestorFromDfsIntervalsEstablished(first, second).
has_value()) {
810 for (std::size_t index =
numSlots; index > 0; --index) {
816 NodeId representative = node;
817 while (
setParents[
static_cast<std::size_t
>(representative)] != representative) {
818 representative =
setParents[
static_cast<std::size_t
>(representative)];
820 while (
setParents[
static_cast<std::size_t
>(node)] != node) {
822 setParents[
static_cast<std::size_t
>(node)] = representative;
825 return representative;
854 setParents[
static_cast<std::size_t
>(rootNodeId_)] = rootNodeId_;
855 ancestors[
static_cast<std::size_t
>(rootNodeId_)] = rootNodeId_;
856 traversalStack.push_back({rootNodeId_, firstChild_[
static_cast<std::size_t
>(rootNodeId_)]});
862 frame.nextChild = nextSibling_[
static_cast<std::size_t
>(
child)];
870 const std::size_t
nodeIndex =
static_cast<std::size_t
>(node);
895 lowestCommonAncestorsEstablished(std::span<
const std::pair<NodeId, NodeId>>
queries)
const {
897 forEachLowestCommonAncestorEstablished(
914 template <
typename QueryAccessor,
typename ResultConsumer>
930 static_cast<long double>(nodeParent_.size()) * 30.0L +
static_cast<long double>(
numQueries) * 24.0L;
938 if (!lowestCommonAncestorFromDfsIntervalsEstablished(first, second).
has_value()) {
948 forEachLowestCommonAncestorEstablished(
957 const std::optional<NodeId>
intervalResult = lowestCommonAncestorFromDfsIntervalsEstablished(first, second);
970 static_cast<std::size_t
>(std::numeric_limits<std::uint32_t>::max() / 2);
972 static_cast<long double>(nodeParent_.size()) * 30.0L +
static_cast<long double>(
numUnresolvedQueries) * 28.0L;
976 if (!lowestCommonAncestorFromDfsIntervalsEstablished(first, second).
has_value()) {
994 forEachLowestCommonAncestorEstablished(
996 [&](std::size_t
queryIndex,
NodeId lca) { consumeLca(unresolvedQueryIndices[queryIndex], lca); },
true);
1027 bumpTopologyVersion();
1048 nextSibling_[
prev] = next;
1053 prevSibling_[next] =
prev;
1061 bumpTopologyVersion();
1090 firstChild_[
toId] = next;
1092 nextSibling_[
prev] = next;
1098 prevSibling_[next] =
prev;
1140 numChildrenByNode_[
fromId] = 0;
1141 bumpTopologyVersion();
1157 if (numChildrenByNode_[
nodeSlot] != 0 || properPartCardinalityByNode_[
nodeSlot] != 0) {
1168 inline NodeId allocateNode() {
1169 if (freeNodeIds_.empty()) {
1175 invalidateDfsIntervalCache();
1176 bumpNodeStructureVersion();
1189 inline NodeId createDetachedNode() {
1193 invalidateDfsIntervalCache();
1194 bumpNodeStructureVersion();
1212 properPartCardinalityByNode_[
static_cast<std::size_t
>(
childSlotId)] == 0) {
1213 freeNodeIds_.reserve(freeNodeIds_.size() + 1);
1217 invalidateDfsIntervalCache();
1314 bumpProperPartVersion();
1331 bumpProperPartVersion();
1347 if (rootNodeId_ !=
InvalidNode && !isFreeSlot(rootNodeId_)) {
1348 nodeParent_[rootNodeId_] = rootNodeId_;
1360 bumpTopologyVersion();
1379 throw std::invalid_argument(
"Native topology pixel domain must match the attached 2D grid.");
1381 if (
nodeParent.size() >
static_cast<std::size_t
>(std::numeric_limits<NodeId>::max())) {
1382 throw std::invalid_argument(
"Native topology internal-node domain exceeds NodeId range.");
1384 if (
smallestNodeMap.size() >
static_cast<std::size_t
>(std::numeric_limits<PixelId>::max())) {
1385 throw std::invalid_argument(
"Native topology pixel domain exceeds PixelId range.");
1388 case MorphologicalTreeKind::Generic:
1389 case MorphologicalTreeKind::MaxTree:
1390 case MorphologicalTreeKind::MinTree:
1391 case MorphologicalTreeKind::TreeOfShapes:
1392 case MorphologicalTreeKind::UnrestrictedResidualTree:
1393 case MorphologicalTreeKind::SaturatedResidualTree:
1396 throw std::invalid_argument(
"Native topology kind is not supported.");
1398 const int numNodeSlots =
static_cast<int>(
nodeParent.size());
1400 if (numNodeSlots <= 0) {
1401 throw std::invalid_argument(
"Native topology import requires at least one internal node.");
1404 throw std::invalid_argument(
"Native topology import requires at least one pixel.");
1407 throw std::invalid_argument(
"Native topology import requires a valid root node id.");
1413 if (!gridDomain2D_) {
1414 throw std::invalid_argument(std::string(
context) +
" requires an attached 2D grid domain.");
1417 throw std::invalid_argument(std::string(
context) +
" must match the attached 2D grid.");
1424 if (
context->infinityPixel < 0 ||
static_cast<std::size_t
>(
context->infinityPixel) >= gridDomain2D_->size(
"Saturated residual domain")) {
1425 throw std::invalid_argument(
"SaturatedResidualContext infinity pixel must belong to the attached 2D grid.");
1427 }
else if (
const auto* convention = std::get_if<TopographicConvention>(&semantics_.
constructionContext)) {
1428 if (convention->infinityPixel < 0) {
1429 throw std::invalid_argument(
"TopographicConvention infinity pixel must be non-negative.");
1431 if (!gridDomain2D_) {
1432 throw std::invalid_argument(
"TopographicConvention requires an attached 2D grid domain.");
1434 const std::int64_t
extension = convention->domainExtension == TopographicDomainExtension::ExteriorRing ? 1 : -1;
1435 const std::int64_t
activeRows = 2 *
static_cast<std::int64_t
>(gridDomain2D_->rows) +
extension;
1438 throw std::invalid_argument(
"TopographicConvention infinity pixel must belong to the active topographic domain.");
1440 if (
const auto* immersion = std::get_if<ComplementaryGridImmersion>(&convention->immersion)) {
1441 requireMatchingGrid(immersion->complementaryAdjacencies.minAdjacency,
"Topographic minimum adjacency");
1442 requireMatchingGrid(immersion->complementaryAdjacencies.maxAdjacency,
"Topographic maximum adjacency");
1448 firstChild_.assign(
static_cast<size_t>(numNodeSlots),
InvalidNode);
1449 nextSibling_.assign(
static_cast<size_t>(numNodeSlots),
InvalidNode);
1450 prevSibling_.assign(
static_cast<size_t>(numNodeSlots),
InvalidNode);
1451 lastChild_.assign(
static_cast<size_t>(numNodeSlots),
InvalidNode);
1452 numChildrenByNode_.assign(
static_cast<size_t>(numNodeSlots), 0);
1453 alive_.assign(
static_cast<size_t>(numNodeSlots), 1);
1454 freeNodeIds_.clear();
1455 properHead_.assign(
static_cast<size_t>(numNodeSlots),
InvalidPixel);
1456 properTail_.assign(
static_cast<size_t>(numNodeSlots),
InvalidPixel);
1457 properPartCardinalityByNode_.assign(
static_cast<size_t>(numNodeSlots), 0);
1462 numNodes_ =
static_cast<int>(numNodeSlots);
1469 throw std::invalid_argument(
"Native topology import found a detached self-parented non-root node.");
1475 throw std::invalid_argument(
"Native topology import found a parent outside the internal-node domain.");
1478 prevSibling_[
static_cast<size_t>(
nodeId)] = lastChild_[
static_cast<size_t>(
parentId)];
1482 nextSibling_[
static_cast<size_t>(lastChild_[
static_cast<size_t>(
parentId)])] =
nodeId;
1485 ++numChildrenByNode_[
static_cast<size_t>(
parentId)];
1488 throw std::invalid_argument(
"Native topology import must encode exactly one self-parented root.");
1491 rebuildProperPartLinksFromSmallestNodeMap();
1492 invalidateDfsIntervalCache();
1493 invalidateAllIterators();
1494 preservedExternalNodeIdOffset_.reset();
1508 if (
nodeParent.size() >
static_cast<std::size_t
>(std::numeric_limits<NodeId>::max())) {
1509 throw std::invalid_argument(
"Native topology internal-node domain exceeds NodeId range.");
1511 const int numNodeSlots =
static_cast<int>(
nodeParent.size());
1514 throw std::invalid_argument(
"Native topology import found a smallest-node-map entry outside the internal-node domain.");
1535 detail::NativeTopologyProof&& topologyProof) {
1556 std::move(topologyProof));
1574 semantics_ = std::move(
other.semantics_);
1576 gridDomain2D_ = std::move(
other.gridDomain2D_);
1577 other.gridDomain2D_.reset();
1578 numNodes_ = std::exchange(
other.numNodes_, 0);
1579 preservedExternalNodeIdOffset_ = std::move(
other.preservedExternalNodeIdOffset_);
1580 other.preservedExternalNodeIdOffset_.reset();
1581 editSessionOpen_ =
false;
1582 editValidationStatistics_ =
other.editValidationStatistics_;
1584 smallestNodeMap_ = std::move(
other.smallestNodeMap_);
1585 nodeParent_ = std::move(
other.nodeParent_);
1586 firstChild_ = std::move(
other.firstChild_);
1587 nextSibling_ = std::move(
other.nextSibling_);
1588 prevSibling_ = std::move(
other.prevSibling_);
1589 lastChild_ = std::move(
other.lastChild_);
1590 numChildrenByNode_ = std::move(
other.numChildrenByNode_);
1591 alive_ = std::move(
other.alive_);
1592 freeNodeIds_ = std::move(
other.freeNodeIds_);
1593 properHead_ = std::move(
other.properHead_);
1594 properTail_ = std::move(
other.properTail_);
1595 properPartCardinalityByNode_ = std::move(
other.properPartCardinalityByNode_);
1596 nextProperPart_ = std::move(
other.nextProperPart_);
1597 prevProperPart_ = std::move(
other.prevProperPart_);
1599 dfsIntervalCache_ = std::move(
other.dfsIntervalCache_);
1600 other.dfsIntervalCache_ = {};
1602 other.lcaCache_.reset();
1603 nodeSupportMetadataCache_ = std::move(
other.nodeSupportMetadataCache_);
1604 other.nodeSupportMetadataCache_ = {};
1606 nodeStructureVersion_ =
other.nodeStructureVersion_;
1607 topologyVersion_ =
other.topologyVersion_;
1608 properPartVersion_ =
other.properPartVersion_;
1609 mutationVersion_ =
other.mutationVersion_;
1613 ++
other.nodeStructureVersion_;
1614 ++
other.topologyVersion_;
1615 ++
other.properPartVersion_;
1616 ++
other.mutationVersion_;
1641 other.requireNotEditing(
"MorphologicalTree move construction");
1642 moveCommittedStateFrom(std::move(
other));
1656 other.requireNotEditing(
"MorphologicalTree move assignment source");
1657 if (
this != &
other) {
1662 moveCommittedStateFrom(std::move(
other));
1731 cloned.rootNodeId_ = rootNodeId_;
1732 cloned.semantics_ = semantics_;
1733 cloned.gridDomain2D_ = gridDomain2D_;
1734 cloned.numNodes_ = numNodes_;
1735 cloned.preservedExternalNodeIdOffset_ = preservedExternalNodeIdOffset_;
1736 cloned.editSessionOpen_ =
false;
1737 cloned.editValidationStatistics_ = editValidationStatistics_;
1738 cloned.smallestNodeMap_ = smallestNodeMap_;
1739 cloned.nodeParent_ = nodeParent_;
1740 cloned.firstChild_ = firstChild_;
1741 cloned.nextSibling_ = nextSibling_;
1742 cloned.prevSibling_ = prevSibling_;
1743 cloned.lastChild_ = lastChild_;
1744 cloned.numChildrenByNode_ = numChildrenByNode_;
1746 cloned.freeNodeIds_ = freeNodeIds_;
1747 cloned.properHead_ = properHead_;
1748 cloned.properTail_ = properTail_;
1749 cloned.properPartCardinalityByNode_ = properPartCardinalityByNode_;
1750 cloned.nextProperPart_ = nextProperPart_;
1751 cloned.prevProperPart_ = prevProperPart_;
1752 cloned.dfsIntervalCache_ = dfsIntervalCache_;
1753 cloned.lcaCache_.reset();
1754 cloned.nodeSupportMetadataCache_ = nodeSupportMetadataCache_;
1755 cloned.nodeStructureVersion_ = nodeStructureVersion_;
1756 cloned.topologyVersion_ = topologyVersion_;
1757 cloned.properPartVersion_ = properPartVersion_;
1758 cloned.mutationVersion_ = mutationVersion_;
1763 class AliveNodeIterator;
1764 class AliveNodeRange;
1765 class ChildrenIterator;
1766 class ChildrenRange;
1767 class ProperPartIterator;
1768 class ProperPartRange;
1769 class NodeSupportIterator;
1770 class NodeSupportRange;
1771 class PostOrderNodeIterator;
1772 class PostOrderNodeRange;
1773 class BreadthFirstNodeIterator;
1774 class BreadthFirstNodeRange;
1775 class AncestorNodeIterator;
1776 class AncestorNodeRange;
1777 class PathBetweenNodesIterator;
1778 class PathBetweenNodesRange;
1779 class SubtreeNodeIterator;
1780 class SubtreeNodeRange;
1781 class DescendantNodeRange;
1801 throw std::logic_error(std::string(
context) +
" cannot be used after the referenced tree topology has changed."));
1818 inline int numPixels()
const {
return static_cast<int>(smallestNodeMap_.size()); }
1830 if (!preservedExternalNodeIdOffset_) {
1831 throw std::runtime_error(
"This tree does not preserve an imported Higra node-id space.");
1847 case NodeIdSpace::MorphologicalTree:
1849 case NodeIdSpace::Higra:
1852 throw std::runtime_error(
"Unknown NodeIdSpace.");
1868 return *preservedExternalNodeIdOffset_ +
nodeId;
1946 requireAliveNode(
nodeId,
"MorphologicalTree::numChildren");
1947 return numChildrenByNode_[
nodeId];
1957 requireAliveNode(
nodeId,
"MorphologicalTree::numDescendants");
1958 ensureDfsIntervalCache();
1959 return (dfsIntervalCache_.exitIndex[
nodeId] - dfsIntervalCache_.entryIndex[
nodeId] - 1) / 2;
1969 requireAliveNode(
nodeId,
"MorphologicalTree::numSiblings");
1984 requireAliveNode(
nodeId,
"MorphologicalTree::dfsEntryIndex");
1985 ensureDfsIntervalCache();
1986 return dfsIntervalCache_.entryIndex[
nodeId];
1996 requireAliveNode(
nodeId,
"MorphologicalTree::dfsExitIndex");
1997 ensureDfsIntervalCache();
1998 return dfsIntervalCache_.exitIndex[
nodeId];
2008 requireAliveNode(
nodeId,
"MorphologicalTree::getFirstChild");
2009 return firstChild_[
nodeId];
2019 requireAliveNode(
nodeId,
"MorphologicalTree::getNextSibling");
2020 return nextSibling_[
nodeId];
2038 requireAliveNode(
nodeId,
"MorphologicalTree::properPartCardinality");
2039 return properPartCardinalityByNode_[
nodeId];
2058 requireAliveNode(
parentNodeId,
"MorphologicalTree::hasChild");
2059 requireAliveNode(
childId,
"MorphologicalTree::hasChild");
2072 requireAliveNode(
nodeId,
"MorphologicalTree::parent");
2093 if constexpr (contract::validationsEnabled) {
2096 return smallestNodeMap_[
static_cast<size_t>(pixel)];
2112 std::vector<NodeId>
leaves;
2117 s.push(this->rootNodeId_);
2119 while (!
s.empty()) {
2121 if (numChildrenByNode_[
id] == 0) {
2206 throw std::logic_error(std::string(
context) +
" requires a committed MorphologicalTree; an edit session is still open."));
2219 if (numNodes_ == 0) {
2258 if (!gridDomain2D_) {
2259 throw std::invalid_argument(std::string(
context) +
" requires a regular 2D pixel domain.");
2261 return *gridDomain2D_;
2286 if (numNodes_ <= 0) {
2287 throw std::runtime_error(
"Connected-tree validation requires at least one live node.");
2290 throw std::runtime_error(
"Connected-tree validation requires a live root.");
2292 if (nodeParent_[rootNodeId_] != rootNodeId_) {
2293 throw std::runtime_error(
"Connected-tree validation requires the root to point to itself.");
2308 if (
nodeId == rootNodeId_) {
2312 throw std::runtime_error(
"Connected-tree validation found an alive node with no parent.");
2315 throw std::runtime_error(
"Connected-tree validation found a detached alive node.");
2318 throw std::runtime_error(
"Connected-tree validation found an alive node whose parent is outside the alive node domain.");
2324 throw std::runtime_error(
"Connected-tree validation found numNodes() out of sync with the alive node slots.");
2338 throw std::runtime_error(
"Connected-tree validation found a child list that references a non-alive node.");
2341 throw std::runtime_error(
"Connected-tree validation found a child whose parent pointer disagrees with the child list.");
2344 throw std::runtime_error(
"Connected-tree validation found broken previous-sibling links.");
2347 throw std::runtime_error(
"Connected-tree validation found a node referenced by multiple child lists.");
2353 throw std::runtime_error(
"Connected-tree validation found a cycle in a child list.");
2359 throw std::runtime_error(
"Connected-tree validation found an empty child list with a non-empty tail pointer.");
2363 throw std::runtime_error(
"Connected-tree validation found an incorrect last-child pointer.");
2366 throw std::runtime_error(
"Connected-tree validation found a child list whose tail still points to a next sibling.");
2371 throw std::runtime_error(
"Connected-tree validation found an incorrect child count cache.");
2374 throw std::runtime_error(
"Connected-tree validation found child lists out of sync with parent pointers.");
2378 if (
seenAsChild[
static_cast<size_t>(rootNodeId_)] != 0) {
2379 throw std::runtime_error(
"Connected-tree validation found the root inside a child list.");
2386 throw std::runtime_error(
"Connected-tree validation found a non-root alive node missing from the child lists.");
2390 std::vector<NodeId> traversal;
2391 traversal.reserve(
static_cast<size_t>(
aliveCount));
2392 traversal.push_back(rootNodeId_);
2393 for (
size_t index = 0; index < traversal.size(); ++index) {
2399 if (traversal.size() !=
static_cast<size_t>(
aliveCount)) {
2400 throw std::runtime_error(
"Connected-tree validation found nodes outside the rooted component or a cycle.");
2404 const NodeId smallestNodeId = smallestNodeMap_[
static_cast<size_t>(pixel)];
2405 if (!
isAlive(smallestNodeId)) {
2406 throw std::runtime_error(
"Connected-tree validation found a pixel without a live smallest node.");
2420 pixel = nextProperPart_[
static_cast<size_t>(pixel)]) {
2422 throw std::runtime_error(
"Connected-tree validation found an invalid pixel id in a proper-part list.");
2424 if (smallestNodeMap_[
static_cast<size_t>(pixel)] !=
nodeId) {
2425 throw std::runtime_error(
"Connected-tree validation found a proper-part list that disagrees with the smallest-node map.");
2428 throw std::runtime_error(
"Connected-tree validation found broken previous-proper-part links.");
2431 throw std::runtime_error(
"Connected-tree validation found a proper part referenced multiple times.");
2437 throw std::runtime_error(
"Connected-tree validation found a cycle in a proper-part list.");
2443 throw std::runtime_error(
"Connected-tree validation found an empty proper-part list with a non-empty tail pointer.");
2447 throw std::runtime_error(
"Connected-tree validation found an incorrect proper-part tail pointer.");
2450 throw std::runtime_error(
"Connected-tree validation found a proper-part list whose tail still points forward.");
2455 throw std::runtime_error(
"Connected-tree validation found an incorrect direct proper-part count cache.");
2458 throw std::runtime_error(
"Connected-tree validation found proper-part lists out of sync with the smallest-node map.");
2464 throw std::runtime_error(
"Connected-tree validation found a pixel missing from the proper-part lists.");
2469 for (
auto it = traversal.rbegin();
it != traversal.rend(); ++
it) {
2472 throw std::runtime_error(
"Connected-tree validation found a live node whose subtree support is empty.");
2474 if (
nodeId != rootNodeId_) {
2490 }
catch (
const std::exception&
ex) {
2491 return {
false,
ex.what()};
2493 return {
false,
"Connected-tree validation failed with an unknown error."};
2504 if (numNodes_ <= 0) {
2508 if (
isAlive(
nodeId) && properPartCardinalityByNode_[
static_cast<std::size_t
>(
nodeId)] == 0) {
2523 if (
isAlive(
nodeId) && properPartCardinalityByNode_[
static_cast<std::size_t
>(
nodeId)] == 0) {
2524 throw std::runtime_error(
"Tree-of-partial-partitions validation found a node with an empty proper part: " + std::to_string(
nodeId) +
".");
2537 requireAliveNode(
u,
"MorphologicalTree::isAncestor");
2538 requireAliveNode(
v,
"MorphologicalTree::isAncestor");
2539 ensureDfsIntervalCache();
2542 return dfsIntervalCache_.entryIndex[
slotU] <= dfsIntervalCache_.entryIndex[
slotV] &&
2543 dfsIntervalCache_.exitIndex[
slotU] >= dfsIntervalCache_.exitIndex[
slotV];
2554 requireAliveNode(
u,
"MorphologicalTree::isDescendant");
2555 requireAliveNode(
v,
"MorphologicalTree::isDescendant");
2556 ensureDfsIntervalCache();
2559 return dfsIntervalCache_.entryIndex[
slotV] <= dfsIntervalCache_.entryIndex[
slotU] &&
2560 dfsIntervalCache_.exitIndex[
slotV] >= dfsIntervalCache_.exitIndex[
slotU];
2616 return lowestCommonAncestorEstablished(
u,
v);
2630 [[
nodiscard]]
inline std::vector<NodeId>
2632 std::vector<std::pair<NodeId, NodeId>>
liveQueries;
2667 requireAliveNode(
nodeId,
"MorphologicalTree::children");
2678 requireAliveNode(
nodeId,
"MorphologicalTree::properPart");
2692 requireAliveNode(
nodeId,
"MorphologicalTree::nodeSupport");
2710 requireAliveNode(
rootNodeId,
"MorphologicalTree::postOrder");
2728 requireAliveNode(
rootNodeId,
"MorphologicalTree::breadthFirstTraversal");
2742 requireAliveNode(
nodeId,
"MorphologicalTree::ancestors");
2754 requireAliveNode(
sourceNodeId,
"MorphologicalTree::getPathBetweenNodes");
2755 requireAliveNode(
targetNodeId,
"MorphologicalTree::getPathBetweenNodes");
2766 requireAliveNode(
nodeId,
"MorphologicalTree::subtreeNodes");
2777 requireAliveNode(
nodeId,
"MorphologicalTree::descendants");
2806 requireAliveNonRootNode(
nodeId,
"MorphologicalTree::pruneNode");
2809 throw std::invalid_argument(
"MorphologicalTree::pruneNode requires an attached non-root node.");
2816 freeNodeIds_.reserve(freeNodeIds_.size() +
releaseCount);
2854 requireAliveNonRootNode(
nodeId,
"MorphologicalTree::mergeNodeIntoParent");
2857 throw std::invalid_argument(
"MorphologicalTree::mergeNodeIntoParent requires an attached non-root node.");
2861 freeNodeIds_.reserve(freeNodeIds_.size() + 1);
2866 bumpProperPartVersion();
2914 std::vector<NodeId> euler_;
2916 std::vector<int> depth_;
2918 std::vector<int> firstOccurrence_;
2920 std::vector<int> log2_;
2922 std::vector<int> sparseTable_;
2924 int sparseTableStride_ = 0;
2935 std::vector<std::pair<NodeId, NodeId>>
stack;
2937 firstOccurrence_[
static_cast<size_t>(
nodeId)] =
static_cast<int>(euler_.size());
2938 euler_.push_back(
nodeId);
2940 while (!
stack.empty()) {
2942 if (
child == InvalidNode) {
2944 if (!
stack.empty()) {
2949 stack.back().
second = tree_->nextSibling_[child];
2950 firstOccurrence_[
static_cast<size_t>(child)] =
static_cast<int>(euler_.size());
2951 euler_.push_back(child);
2952 depth_.push_back(currentDepth +
static_cast<int>(stack.size()));
2953 stack.emplace_back(child, tree_->firstChild_[child]);
2961 void buildSparseTable() {
2962 const int n =
static_cast<int>(depth_.size());
2965 sparseTable_.clear();
2966 sparseTableStride_ = 0;
2970 log2_.assign(
static_cast<size_t>(n + 1), 0);
2971 for (
int i = 2; i <= n; ++i) {
2972 log2_[
static_cast<size_t>(i)] = log2_[
static_cast<size_t>(i / 2)] + 1;
2975 sparseTableStride_ = 1;
2976 while ((1 << sparseTableStride_) <= n) {
2977 ++sparseTableStride_;
2980 sparseTable_.assign(
static_cast<size_t>(n) *
static_cast<size_t>(sparseTableStride_), 0);
2982 for (
int i = 0; i < n; ++i) {
2983 sparseTable_[sparseTableIndex(i, 0)] = i;
2986 for (
int j = 1; j < sparseTableStride_; ++j) {
2987 const int blockSize = 1 << j;
2988 const int halfBlock = blockSize >> 1;
2989 for (
int i = 0; i + blockSize <= n; ++i) {
2990 const int leftIndex = sparseTable_[sparseTableIndex(i, j - 1)];
2991 const int rightIndex = sparseTable_[sparseTableIndex(i + halfBlock, j - 1)];
2992 sparseTable_[sparseTableIndex(i, j)] =
2993 depth_[
static_cast<size_t>(leftIndex)] <= depth_[
static_cast<size_t>(rightIndex)] ? leftIndex : rightIndex;
3005 std::size_t sparseTableIndex(
int row,
int column)
const {
3006 return static_cast<std::size_t
>(row) *
static_cast<std::size_t
>(sparseTableStride_) +
static_cast<std::size_t
>(column);
3016 int rmq(
int left,
int right)
const {
3017 const int length = right - left + 1;
3018 const int logLength = log2_[
static_cast<size_t>(length)];
3019 const int leftIndex = sparseTable_[sparseTableIndex(left, logLength)];
3020 const int rightIndex = sparseTable_[sparseTableIndex(right - (1 << logLength) + 1, logLength)];
3021 return depth_[
static_cast<size_t>(leftIndex)] <= depth_[
static_cast<size_t>(rightIndex)] ? leftIndex : rightIndex;
3030 explicit LCAEulerRMQ(
const MorphologicalTree* tree) : tree_(tree) {
3031 if (tree_ ==
nullptr || tree_->
root() == InvalidNode) {
3035 euler_.reserve(
static_cast<size_t>(std::max(0, 2 * tree_->
numNodes() - 1)));
3036 depth_.reserve(
static_cast<size_t>(std::max(0, 2 * tree_->
numNodes() - 1)));
3039 depthFirstTraversal(tree_->
root(), 0);
3050 NodeId findLowestCommonAncestor(NodeId u, NodeId v)
const {
3051 const int firstU = firstOccurrence_[
static_cast<size_t>(u)];
3052 const int firstV = firstOccurrence_[
static_cast<size_t>(v)];
3053 if (firstU < 0 || firstV < 0) {
3060 std::swap(left, right);
3062 return euler_[
static_cast<size_t>(rmq(left, right))];
3081 std::size_t expectedVersion_ = 0;
3091 T_->checkNodeIteratorVersion(expectedVersion_);
3095 if (current_ >= end_) {
3136 T_->checkNodeIteratorVersion(expectedVersion_);
3150 T_->checkNodeIteratorVersion(expectedVersion_);
3183 std::size_t expectedVersion_ = 0;
3227 std::size_t expectedVersion_ = 0;
3262 T_->checkTopologyIteratorVersion(expectedVersion_);
3264 currentLocal_ = T_->nextSibling_[currentLocal_];
3275 T_->checkTopologyIteratorVersion(expectedVersion_);
3276 return currentLocal_;
3306 std::size_t expectedVersion_ = 0;
3349 std::size_t expectedVersion_ = 0;
3384 T_->checkProperPartIteratorVersion(expectedVersion_);
3386 currentProperPart_ = T_->nextProperPart_[currentProperPart_];
3397 T_->checkProperPartIteratorVersion(expectedVersion_);
3398 return currentProperPart_;
3428 std::size_t expectedVersion_ = 0;
3469 std::vector<NodeId> nodeStack_;
3473 std::size_t expectedTopologyVersion_ = 0;
3475 std::size_t expectedProperPartVersion_ = 0;
3480 void checkVersions()
const {
3481 T_->checkTopologyIteratorVersion(expectedTopologyVersion_);
3482 T_->checkProperPartIteratorVersion(expectedProperPartVersion_);
3496 nodeStack_.push_back(*
it);
3505 while (currentProperPart_ ==
InvalidPixel && !nodeStack_.empty()) {
3507 nodeStack_.pop_back();
3509 currentProperPart_ = T_->properHead_[
static_cast<size_t>(
nodeId)];
3554 currentProperPart_ = T_->nextProperPart_[
static_cast<size_t>(currentProperPart_)];
3567 return currentProperPart_;
3597 std::size_t expectedTopologyVersion_ = 0;
3599 std::size_t expectedProperPartVersion_ = 0;
3648 std::vector<Item> stack_;
3652 std::size_t expectedVersion_ = 0;
3658 T_->checkTopologyIteratorVersion(expectedVersion_);
3659 while (!stack_.empty()) {
3660 Item&
top = stack_.back();
3661 if (!
top.expanded) {
3662 top.expanded =
true;
3665 stack_.push_back({
child,
false});
3712 T_->checkTopologyIteratorVersion(expectedVersion_);
3713 if (!stack_.empty()) {
3726 T_->checkTopologyIteratorVersion(expectedVersion_);
3757 std::size_t expectedVersion_ = 0;
3800 std::size_t expectedVersion_ = 0;
3838 T_->checkTopologyIteratorVersion(expectedVersion_);
3839 if (!queue_.empty()) {
3854 T_->checkTopologyIteratorVersion(expectedVersion_);
3855 return queue_.front();
3885 std::size_t expectedVersion_ = 0;
3928 std::size_t expectedVersion_ = 0;
3963 T_->checkTopologyIteratorVersion(expectedVersion_);
3965 if (T_->
isRoot(current_)) {
3968 current_ = T_->
parent(current_);
3980 T_->checkTopologyIteratorVersion(expectedVersion_);
4011 std::size_t expectedVersion_ = 0;
4052 const std::vector<NodeId>* path_ =
nullptr;
4054 std::size_t index_ = 0;
4056 std::size_t expectedVersion_ = 0;
4092 T_->checkTopologyIteratorVersion(expectedVersion_);
4105 T_->checkTopologyIteratorVersion(expectedVersion_);
4106 return (*path_)[index_];
4134 std::vector<NodeId> path_;
4136 std::size_t expectedVersion_ = 0;
4171 std::vector<NodeId>
path;
4238 std::vector<NodeId> stack_;
4240 std::size_t expectedVersion_ = 0;
4278 T_->checkTopologyIteratorVersion(expectedVersion_);
4279 if (!stack_.empty()) {
4284 stack_.push_back(
child);
4296 T_->checkTopologyIteratorVersion(expectedVersion_);
4297 return stack_.back();
4327 std::size_t expectedVersion_ = 0;
4370 std::size_t expectedVersion_ = 0;
int PixelId
Pixel identifier type used by source and active construction domains.
int NodeId
Node identifier type used throughout the project.
constexpr NodeId InvalidNode
Sentinel value used to denote an invalid node identifier.
constexpr PixelId InvalidPixel
Sentinel value used to denote an invalid pixel identifier.
#define MMCFILTERS_CONTRACT_REQUIRE(condition,...)
Evaluates a caller precondition and its failure action only in checked builds.
Immutable scientific metadata attached to a morphological tree.
std::variant< NoConstructionContext, SharedAdjacencyContext, SaturatedResidualContext, TopographicConvention > MorphologicalTreeConstructionContext
Tagged construction context retained by morphological-tree semantics.
NodeAltitudeOrder
Global ordering constraint of node altitudes along parent-child arcs.
MorphologicalTreeKind
Declared construction family of one morphological tree.
void validateMorphologicalTreeSemantics(const MorphologicalTreeSemantics &semantics)
Validates the scientific compatibility of a semantics descriptor.
Public construction facade for all high-level morphological trees.
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.
Iterator that walks from a node towards the root.
AncestorNodeIterator()=default
Creates the end/sentinel rootward-path iterator.
AncestorNodeIterator & operator++()
Advances one step toward the root.
AncestorNodeIterator(const MorphologicalTree *tree, NodeId current, std::size_t expectedVersion)
Creates an iterator starting at one node and walking to the root.
bool operator==(const AncestorNodeIterator &other) const
Compares path-to-root iterator positions.
std::input_iterator_tag iterator_category
Standard iterator category exposed for STL compatibility.
bool operator!=(const AncestorNodeIterator &other) const
Compares path-to-root iterator positions for inequality.
NodeId operator*() const
Returns the current node on the rootward path.
std::ptrdiff_t difference_type
Signed distance type exposed for STL compatibility.
Range wrapper for rootward path traversal.
AncestorNodeRange()=default
Creates an empty rootward-path range.
AncestorNodeIterator begin() const
Returns an iterator at the path start node.
AncestorNodeIterator end() const
Returns the rootward-path range sentinel.
AncestorNodeRange(const MorphologicalTree *tree, NodeId start, std::size_t expectedVersion)
Creates a range from start to the connected root.
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.
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 all pixels in one node support.
PixelId operator*() const
Returns the current pixel identifier.
bool operator==(const NodeSupportIterator &other) const
Compares node-support iterator positions.
std::ptrdiff_t difference_type
Signed distance type exposed for STL compatibility.
NodeSupportIterator & operator++()
Advances to the next pixel in the node support.
NodeSupportIterator(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 NodeSupportIterator &other) const
Compares node-support iterator positions for inequality.
NodeSupportIterator()=default
Creates the end/sentinel node-support iterator.
std::forward_iterator_tag iterator_category
Standard iterator category exposed for STL compatibility.
Range wrapper for node-support pixel iteration.
NodeSupportRange(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.
NodeSupportIterator end() const
Returns the node-support range sentinel.
NodeSupportRange()=default
Creates an empty node-support range.
NodeSupportIterator begin() const
Returns an iterator at the first pixel in the node support.
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.
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 pixels in one node's proper part.
ProperPartIterator & operator++()
Advances to the next pixel in the proper part.
ProperPartIterator(const MorphologicalTree *tree, PixelId currentProperPart, std::size_t expectedVersion)
Creates a proper-part iterator starting from a linked-list entry.
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 ProperPartIterator &other) const
Compares proper-part iterator positions for inequality.
ProperPartIterator()=default
Creates the end/sentinel proper-part iterator.
PixelId operator*() const
Returns the current pixel identifier.
bool operator==(const ProperPartIterator &other) const
Compares proper-part iterator positions.
Range wrapper for direct proper-part iteration.
ProperPartRange()=default
Creates an empty direct proper-part range.
ProperPartIterator begin() const
Returns an iterator at the first direct proper part.
ProperPartRange(const MorphologicalTree *tree, PixelId firstProperPart, std::size_t expectedVersion)
Creates a range over one node's direct proper-part list.
ProperPartIterator end() const
Returns the direct proper-part range sentinel.
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 connected-subset tree on a finite pixel domain.
int numRows() const
Returns the number of rows in the regular 2D pixel domain.
void validateTreeOfPartialPartitions() const
Validates the connected-subset invariants and the non-empty proper-part specialization.
const TreeEditValidationStatistics & getEditValidationStatistics() const noexcept
Returns validation-path counters for committed edit sessions.
const SharedAdjacencyContext * sharedAdjacencyContext() const noexcept
Returns the shared-adjacency context, or nullptr.
ProperPartRange properPart(NodeId nodeId) const
Returns a fail-fast range over the pixels in the proper part of nodeId.
int numNodes() const noexcept
Returns the number of currently live nodes.
int numInternalNodeSlots() const
Returns the size of the dense internal-node id domain.
bool isNode(NodeId nodeId) const noexcept
Tests whether nodeId belongs to the internal-node id domain.
SubtreeNodeRange subtreeNodes(NodeId nodeId) const
Returns a pre-order traversal range over the subtree of nodeId.
TreeEditor edit()
Opens the only public entrypoint for staged structural mutations.
PostOrderNodeRange postOrder() const
Returns a post-order traversal range rooted at the connected root.
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.
MorphologicalTree(MorphologicalTree &&other)
Moves a complete committed topology.
bool isStrictComparable(NodeId u, NodeId v) const
Tests whether u and v are strictly comparable in the ancestry order.
int properPartCardinality(NodeId nodeId) const
Returns the cardinality of the proper part of nodeId.
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.
bool hasGridDomain2D() const noexcept
Tests whether pixel ids have an attached row/column layout.
const MorphologicalTreeSemantics & semantics() const noexcept
Returns all generic semantic capabilities of this hierarchy.
int getNumFreeNodeSlots() const
Returns the number of currently reusable node slots.
NodeAltitudeOrder nodeAltitudeOrder() const noexcept
Returns the global parent-to-child altitude ordering constraint.
void validateConnectedRootedTree() const
Validates one rooted tree with a complete smallest-node map and non-empty supports.
MorphologicalTree(detail::MorphologicalTreeConstructionTag, std::span< const NodeId > nodeParent, std::span< const NodeId > smallestNodeMap, NodeId root, MorphologicalTreeSemantics semantics)
Imports a native hierarchy over an abstract finite pixel set.
MorphologicalTree & operator=(const MorphologicalTree &)=delete
Copy assignment is disabled to keep topology storage explicit.
PostOrderNodeRange postOrder(NodeId rootNodeId) const
Returns a post-order traversal range rooted at rootNodeId.
NodeId getHigraNodeId(NodeId nodeId) const noexcept
Returns the preserved imported Higra node id for one live tree node.
NodeSupportRange nodeSupport(NodeId nodeId) const
Returns a fail-fast range over the pixels in the support of nodeId.
int numDescendants(NodeId nodeId) const
Returns the number of internal descendants of nodeId.
bool isComparable(NodeId u, NodeId v) const
Tests whether u and v are comparable in the ancestry order.
bool isEditing() const noexcept
Tests whether a staged edit session is currently open.
MorphologicalTree clone() const
Creates an independent copy of the structural tree state.
int numSiblings(NodeId nodeId) const
Returns the number of siblings of nodeId.
void pruneNode(NodeId nodeId)
Prunes the subtree of nodeId, moving all its support to the parent.
int numPixels() const
Returns the cardinality of the pixel domain.
DescendantNodeRange descendants(NodeId nodeId) const
Returns a range over all proper descendants of nodeId.
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.
std::vector< NodeId > leaves() const
Returns all live leaf nodes in the current hierarchy.
const TopographicConvention * topographicConvention() const noexcept
Returns the topographic convention, or nullptr.
bool isTreeOfPartialPartitions() const
Tests whether every live node has a non-empty proper part.
AncestorNodeRange ancestors(NodeId nodeId) const
Returns nodeId and its proper ancestors through the connected root.
bool isPixel(PixelId pixel) const noexcept
Tests whether pixel belongs to the pixel domain.
int dfsExitIndex(NodeId nodeId) const
Returns the zero-based DFS exit-event index of nodeId.
const SaturatedResidualContext * saturatedResidualContext() const noexcept
Returns the saturated-residual context, or nullptr.
AliveNodeRange aliveNodeIds() const
Returns a fail-fast range over all live node ids.
int dfsEntryIndex(NodeId nodeId) const
Returns the zero-based DFS entry-event index of nodeId.
const MorphologicalTreeConstructionContext & constructionContext() const noexcept
Returns the typed construction context retained by this tree.
bool hasChild(NodeId parentNodeId, NodeId childId) const
Tests whether childId is a direct child of parentNodeId.
BreadthFirstNodeRange breadthFirstTraversal(NodeId rootNodeId) const
Returns a breadth-first traversal range rooted at rootNodeId.
int numLeafNodes() const
Counts the live nodes that currently have no children.
bool isStrictAncestor(NodeId u, NodeId v) const
Tests whether u is a strict ancestor of v.
PathBetweenNodesRange getPathBetweenNodes(NodeId sourceNodeId, NodeId targetNodeId) const
Returns the path that connects sourceNodeId and targetNodeId.
MorphologicalTree(detail::MorphologicalTreeConstructionTag, std::span< const NodeId > nodeParent, std::span< const NodeId > smallestNodeMap, NodeId root, int rows, int columns, MorphologicalTreeSemantics semantics)
Tag-protected import from native mmcfilters topology buffers.
std::span< const NodeId > smallestNodeMap() const noexcept
Returns the pixel-indexed smallest-node map.
std::size_t getMutationVersion() const noexcept
Returns the monotonic mutation counter used by read-only views.
NodeId smallestNode(PixelId pixel) const
Returns the smallest node containing pixel.
MorphologicalTreeKind kind() const noexcept
Returns the optional descriptive hierarchy-family label.
int getNodeIdSpaceSize(NodeIdSpace outputSpace) const
Returns the size of the requested node-id domain.
int numChildren(NodeId nodeId) const
Returns the number of direct children of nodeId.
BreadthFirstNodeRange breadthFirstTraversal() const
Returns a breadth-first traversal range rooted at the connected root.
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.
virtual ~MorphologicalTree()=default
Destroys the topology storage and cached traversal state.
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.
std::vector< NodeId > lowestCommonAncestors(std::span< const std::pair< NodeId, NodeId > > queries) const
Returns lowest common ancestors for a batch of node pairs.
void requireNotEditing(const char *context) const
Rejects operations that require a committed connected topology.
NodeId lowestCommonAncestor(NodeId u, NodeId v) const
Returns the lowest common ancestor of u and v.
const GridDomain2D & requireGridDomain2D(const char *context) const
Returns the regular 2D domain or rejects a geometry-dependent call.
ChildrenRange children(NodeId nodeId) const
Returns a fail-fast range over the direct children of nodeId.
int numColumns() const
Returns the number of columns in the regular 2D pixel domain.
NodeId getFirstChild(NodeId nodeId) const
Returns the first direct child of nodeId, or InvalidNode.
NodeId parent(NodeId nodeId) const
Returns the direct parent of nodeId.
const std::optional< GridDomain2D > & gridDomain2D() const noexcept
Returns the optional regular 2D pixel domain.
bool hasEmptyProperPart(NodeId nodeId) const
Tests whether nodeId has an empty proper part.
MorphologicalTree & operator=(MorphologicalTree &&other)
Move-assigns a complete committed topology.
void mergeNodeIntoParent(NodeId nodeId)
Merges nodeId into its parent and releases the emptied slot.
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 storage explicit.
NodeId root() const
Returns the current hierarchy root.
Immutable regular-grid 2D adjacency with allocation-free traversal.
int getNumColumns() const noexcept
Returns the number of columns in the attached grid domain.
int getNumRows() const noexcept
Returns the number of rows in the attached grid domain.
Thin edit-session facade for multi-step topology updates.
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.
Shape metadata optionally attached to the pixel domain.
Immutable scientific interpretation attached to one morphological tree.
NodeAltitudeOrder nodeAltitudeOrder
Global parent-child altitude order.
MorphologicalTreeConstructionContext constructionContext
Typed retained construction context.
MorphologicalTreeKind kind
Declared construction/result kind.
Records the shared adjacency and infinity pixel of a saturated residual construction.
Records one adjacency shared by both construction polarities.
Complete discrete convention retained by a tree-of-shapes result.
Counts committed edit sessions by validation strategy.
std::size_t incrementalValidationCommits
Number of commits checked with incremental proof validation.
std::size_t completeValidationCommits
Number of commits checked with complete validation.
Non-throwing validation result returned by edit-session checks.
std::string message
Human-readable diagnostic, especially useful when ok is false.