mmcfilters
Public API documentation
Loading...
Searching...
No Matches
MorphologicalTree.hpp
1#pragma once
2
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"
13#include <algorithm>
14#include <cassert>
15#include <cstddef>
16#include <cstdint>
17#include <functional>
18#include <limits>
19#include <list>
20#include <memory>
21#include <optional>
22#include <set>
23#include <span>
24#include <stdexcept>
25#include <string>
26#include <tuple>
27#include <unordered_map>
28#include <unordered_set>
29#include <utility>
30#include <vector>
31
32namespace mmcfilters {
33
34namespace detail {
35class CommittedTreeAccess;
36}
37
41enum class NodeIdSpace { MorphologicalTree, Higra };
42
52 bool ok = false;
53
55 std::string message;
56
60 explicit operator bool() const noexcept { return ok; }
61};
62
75
76enum class TreeEditValidationMode { Complete, Incremental };
77
78// Forward declaration for the edit-session wrapper.
79class TreeEditor;
80
115 private:
116 friend class TreeEditor;
117 friend class MorphologicalTreeFactory;
118 friend class detail::CommittedTreeAccess;
119
120 class LCAEulerRMQ; // Forward declaration for the LCA cache implementation.
121
122 // ========================= Private attributes ========================= //
124 NodeId rootNodeId_ = InvalidNode;
128 std::optional<GridDomain2D> gridDomain2D_;
130 int numNodes_ = 0;
132 std::optional<NodeId> preservedExternalNodeIdOffset_;
134 bool editSessionOpen_ = false;
136 TreeEditValidationStatistics editValidationStatistics_;
137
138 // Smallest-node map, indexed by pixel id [0, numPixels()).
140 std::vector<NodeId> smallestNodeMap_;
141
142 // Parent links, indexed by local node-slot id [0, numInternalNodeSlots()).
144 std::vector<NodeId> nodeParent_;
145
146 // Internal hierarchy linked structure, indexed by local node-slot id [0, numInternalNodeSlots()).
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_;
157
158 // Free slot management and alive-node iteration, indexed by local node-slot id [0, numInternalNodeSlots()).
160 std::vector<uint8_t> alive_;
162 std::vector<NodeId> freeNodeIds_;
163
164 // Per-node proper-part pixel lists, indexed by local node-slot id.
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_;
175
176 // Structural caches for traversal-based queries, indexed by local node-slot id [0, numInternalNodeSlots()).
178 struct DfsIntervalCache {
180 std::vector<int> entryIndex;
182 std::vector<int> exitIndex;
184 bool valid = false;
186 void invalidate() noexcept { valid = false; }
187 };
189 mutable DfsIntervalCache dfsIntervalCache_;
191 mutable std::unique_ptr<LCAEulerRMQ> lcaCache_;
192
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();
205
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();
211 }
212 };
214 mutable NodeSupportMetadataCache nodeSupportMetadataCache_;
215
216 // Version counters for iterator invalidation.
218 std::size_t nodeStructureVersion_ = 0;
220 std::size_t topologyVersion_ = 0;
222 std::size_t properPartVersion_ = 0;
224 std::size_t mutationVersion_ = 0;
225
227 enum class ChildSplicePolicy { AppendToTargetTail, ReplaceSourceSlotWhenDirectChild };
228
229 // ========================= Private methods ========================= //
235 inline NodeId allocateSlot() {
236 if (!freeNodeIds_.empty()) {
237 const NodeId slotId = freeNodeIds_.back();
238 freeNodeIds_.pop_back();
239
240 nodeParent_[slotId] = InvalidNode;
241 firstChild_[slotId] = InvalidNode;
242 nextSibling_[slotId] = InvalidNode;
243 prevSibling_[slotId] = InvalidNode;
244 lastChild_[slotId] = InvalidNode;
245 numChildrenByNode_[slotId] = 0;
246 alive_[slotId] = 1;
247 properHead_[slotId] = InvalidPixel;
248 properTail_[slotId] = InvalidPixel;
249 properPartCardinalityByNode_[slotId] = 0;
250 return slotId;
251 }
252
253 const NodeId slotId = static_cast<NodeId>(nodeParent_.size());
254 nodeParent_.push_back(InvalidNode);
255 firstChild_.push_back(InvalidNode);
256 nextSibling_.push_back(InvalidNode);
257 prevSibling_.push_back(InvalidNode);
258 lastChild_.push_back(InvalidNode);
259 numChildrenByNode_.push_back(0);
260 alive_.push_back(1);
261 properHead_.push_back(InvalidPixel);
262 properTail_.push_back(InvalidPixel);
263 properPartCardinalityByNode_.push_back(0);
264 return slotId;
265 }
266
272 inline void initializeProperPartStorage(size_t numPixels) {
273 nextProperPart_.assign(numPixels, InvalidPixel);
274 prevProperPart_.assign(numPixels, InvalidPixel);
275 }
276
280 inline void invalidateHigraNodeIdSpace() noexcept { preservedExternalNodeIdOffset_.reset(); }
281
291 inline void preserveExternalNodeIdOffset(NodeId internalNodeOffset) {
292 if (internalNodeOffset < 0) {
293 throw std::invalid_argument("An external internal-node offset must be non-negative.");
294 }
295 preservedExternalNodeIdOffset_ = internalNodeOffset;
296 }
297
301 inline void beginEditSession() {
302 if (editSessionOpen_) {
303 throw std::logic_error("A MorphologicalTree edit session is already open.");
304 }
305 editSessionOpen_ = true;
306 }
307
311 inline void endEditSession() noexcept { editSessionOpen_ = false; }
312
318 inline void recordEditCommit(TreeEditValidationMode mode) noexcept {
319 if (mode == TreeEditValidationMode::Incremental) {
320 ++editValidationStatistics_.incrementalValidationCommits;
321 } else {
322 ++editValidationStatistics_.completeValidationCommits;
323 }
324 }
325
331 inline void initializeEmptyStorage(size_t numPixels) {
332 rootNodeId_ = InvalidNode;
333 numNodes_ = 0;
334 smallestNodeMap_.assign(numPixels, InvalidNode);
335
336 nodeParent_.clear();
337 firstChild_.clear();
338 nextSibling_.clear();
339 prevSibling_.clear();
340 lastChild_.clear();
341 numChildrenByNode_.clear();
342 alive_.clear();
343 freeNodeIds_.clear();
344 properHead_.clear();
345 properTail_.clear();
346 properPartCardinalityByNode_.clear();
347 initializeProperPartStorage(numPixels);
348 invalidateHigraNodeIdSpace();
349
350 dfsIntervalCache_.entryIndex.clear();
351 dfsIntervalCache_.exitIndex.clear();
352 invalidateDfsIntervalCache();
353 nodeSupportMetadataCache_.cardinalityByNode.clear();
354 nodeSupportMetadataCache_.smallestPixelByNode.clear();
355 nodeSupportMetadataCache_.invalidate();
356 invalidateAllIterators();
357 }
358
364 inline void releaseSlotStorage(NodeId slotId) {
365 // Grow the free-list before changing the slot. If allocation fails, the
366 // live topology is therefore left untouched instead of terminating from
367 // inside a falsely-noexcept operation or publishing a half-released slot.
368 freeNodeIds_.push_back(slotId);
369 nodeParent_[slotId] = InvalidNode;
370 firstChild_[slotId] = InvalidNode;
371 nextSibling_[slotId] = InvalidNode;
372 prevSibling_[slotId] = InvalidNode;
373 lastChild_[slotId] = InvalidNode;
374 numChildrenByNode_[slotId] = 0;
375 alive_[slotId] = 0;
376 properHead_[slotId] = InvalidPixel;
377 properTail_[slotId] = InvalidPixel;
378 properPartCardinalityByNode_[slotId] = 0;
379 }
380
387 inline bool isFreeSlot(NodeId slotId) const noexcept { return slotId >= 0 && slotId < static_cast<NodeId>(alive_.size()) && alive_[slotId] == 0; }
388
395 inline void requireAliveNode(NodeId nodeId, const char* context) const {
396 MMCFILTERS_CONTRACT_REQUIRE(isAlive(nodeId), throw std::invalid_argument(std::string(context) + " requires a live internal NodeId."));
397 }
398
405 inline void requireAliveNonRootNode(NodeId nodeId, const char* context) const {
406 requireAliveNode(nodeId, context);
407 MMCFILTERS_CONTRACT_REQUIRE(!isRoot(nodeId), throw std::invalid_argument(std::string(context) + " cannot target the root node."));
408 }
409
413 inline void rebuildProperPartLinksFromSmallestNodeMap() {
414 properHead_.assign(nodeParent_.size(), InvalidPixel);
415 properTail_.assign(nodeParent_.size(), InvalidPixel);
416 properPartCardinalityByNode_.assign(nodeParent_.size(), 0);
417 initializeProperPartStorage(smallestNodeMap_.size());
418
419 for (PixelId pixel = 0; pixel < static_cast<PixelId>(smallestNodeMap_.size()); ++pixel) {
420 const NodeId smallestNodeSlotId = smallestNodeMap_[static_cast<std::size_t>(pixel)];
421 if (smallestNodeSlotId == InvalidNode || smallestNodeSlotId >= static_cast<NodeId>(nodeParent_.size()) || isFreeSlot(smallestNodeSlotId)) {
422 continue;
423 }
424
425 const PixelId tailProperPartId = properTail_[smallestNodeSlotId];
427 properHead_[smallestNodeSlotId] = pixel;
428 properTail_[smallestNodeSlotId] = pixel;
429 } else {
430 nextProperPart_[tailProperPartId] = pixel;
431 prevProperPart_[pixel] = tailProperPartId;
432 properTail_[smallestNodeSlotId] = pixel;
433 }
434 properPartCardinalityByNode_[smallestNodeSlotId]++;
435 }
436 }
437
444 inline void unlinkPixelFromProperPart(NodeId smallestNodeSlotId, PixelId pixel) noexcept {
445 const PixelId prev = prevProperPart_[static_cast<size_t>(pixel)];
446 const PixelId next = nextProperPart_[static_cast<size_t>(pixel)];
447
448 if (prev == InvalidPixel) {
449 properHead_[static_cast<size_t>(smallestNodeSlotId)] = next;
450 } else {
451 nextProperPart_[static_cast<size_t>(prev)] = next;
452 }
453
454 if (next == InvalidPixel) {
455 properTail_[static_cast<size_t>(smallestNodeSlotId)] = prev;
456 } else {
457 prevProperPart_[static_cast<size_t>(next)] = prev;
458 }
459
460 nextProperPart_[static_cast<size_t>(pixel)] = InvalidPixel;
461 prevProperPart_[static_cast<size_t>(pixel)] = InvalidPixel;
462 --properPartCardinalityByNode_[static_cast<size_t>(smallestNodeSlotId)];
463 }
464
471 inline void appendDetachedProperPart(NodeId targetSlotId, PixelId pixel) noexcept {
472 smallestNodeMap_[static_cast<size_t>(pixel)] = targetSlotId;
473 const PixelId tail = properTail_[static_cast<size_t>(targetSlotId)];
474 if (tail == InvalidPixel) {
475 properHead_[static_cast<size_t>(targetSlotId)] = pixel;
476 properTail_[static_cast<size_t>(targetSlotId)] = pixel;
477 } else {
478 nextProperPart_[static_cast<size_t>(tail)] = pixel;
479 prevProperPart_[static_cast<size_t>(pixel)] = tail;
480 properTail_[static_cast<size_t>(targetSlotId)] = pixel;
481 }
482 ++properPartCardinalityByNode_[static_cast<size_t>(targetSlotId)];
483 }
484
491 inline void spliceProperPartsSlots(NodeId targetSlotId, NodeId sourceSlotId) noexcept {
492 const PixelId sourceHead = properHead_[static_cast<size_t>(sourceSlotId)];
493 if (sourceHead == InvalidPixel) {
494 return;
495 }
496
497 for (PixelId pixel = sourceHead; pixel != InvalidPixel; pixel = nextProperPart_[static_cast<size_t>(pixel)]) {
498 smallestNodeMap_[static_cast<size_t>(pixel)] = targetSlotId;
499 }
500
501 const PixelId sourceTail = properTail_[static_cast<size_t>(sourceSlotId)];
502 const int sourceCount = properPartCardinalityByNode_[static_cast<size_t>(sourceSlotId)];
503 const PixelId targetTail = properTail_[static_cast<size_t>(targetSlotId)];
504
505 if (targetTail == InvalidPixel) {
506 properHead_[static_cast<size_t>(targetSlotId)] = sourceHead;
507 properTail_[static_cast<size_t>(targetSlotId)] = sourceTail;
508 } else {
509 nextProperPart_[static_cast<size_t>(targetTail)] = sourceHead;
510 prevProperPart_[static_cast<size_t>(sourceHead)] = targetTail;
511 properTail_[static_cast<size_t>(targetSlotId)] = sourceTail;
512 }
513
514 properPartCardinalityByNode_[static_cast<size_t>(targetSlotId)] += sourceCount;
515 properHead_[static_cast<size_t>(sourceSlotId)] = InvalidPixel;
516 properTail_[static_cast<size_t>(sourceSlotId)] = InvalidPixel;
517 properPartCardinalityByNode_[static_cast<size_t>(sourceSlotId)] = 0;
518 }
519
525 inline void releaseSlotNode(NodeId slotNodeId) {
526 releaseSlotStorage(slotNodeId);
527 numNodes_--;
528 invalidateDfsIntervalCache();
529 bumpNodeStructureVersion();
530 }
531
535 inline void invalidateAllIterators() noexcept {
536 nodeStructureVersion_ = 0;
537 topologyVersion_ = 0;
538 properPartVersion_ = 0;
539 ++mutationVersion_;
540 lcaCache_.reset();
541 nodeSupportMetadataCache_.invalidate();
542 }
543
547 inline void invalidateLcaCache() const noexcept { lcaCache_.reset(); }
548
552 inline void bumpNodeStructureVersion() noexcept {
553 ++nodeStructureVersion_;
554 ++mutationVersion_;
555 invalidateLcaCache();
556 invalidateHigraNodeIdSpace();
557 }
558
562 inline void bumpTopologyVersion() noexcept {
563 ++topologyVersion_;
564 ++mutationVersion_;
565 invalidateLcaCache();
566 invalidateDfsIntervalCache();
567 invalidateHigraNodeIdSpace();
568 }
569
573 inline void bumpProperPartVersion() noexcept {
574 ++properPartVersion_;
575 ++mutationVersion_;
576 invalidateHigraNodeIdSpace();
577 }
578
584 inline void checkNodeIteratorVersion([[maybe_unused]] std::size_t expectedVersion) const {
585 assert(expectedVersion == nodeStructureVersion_ && "Alive-node iterator invalidated by node-structure mutation.");
586 }
587
593 inline void checkTopologyIteratorVersion([[maybe_unused]] std::size_t expectedVersion) const {
594 assert(expectedVersion == topologyVersion_ && "Topology iterator invalidated by tree-structure mutation.");
595 }
596
602 inline void checkProperPartIteratorVersion([[maybe_unused]] std::size_t expectedVersion) const {
603 assert(expectedVersion == properPartVersion_ && "Proper-parts iterator invalidated by proper-part mutation.");
604 }
605
609 inline void invalidateDfsIntervalCache() const noexcept { dfsIntervalCache_.invalidate(); }
610
614 inline void recomputeDfsIntervalCache() const {
615 dfsIntervalCache_.entryIndex.assign(nodeParent_.size(), -1);
616 dfsIntervalCache_.exitIndex.assign(nodeParent_.size(), -1);
617
618 if (rootNodeId_ == InvalidNode) {
619 dfsIntervalCache_.valid = true;
620 return;
621 }
622
623 // Explicit frames preserve child order without using the call stack on deep trees.
624 int nextDfsEventIndex = 0;
625 std::vector<std::pair<NodeId, NodeId>> stack;
626 stack.emplace_back(rootNodeId_, firstChild_[rootNodeId_]);
627 dfsIntervalCache_.entryIndex[rootNodeId_] = nextDfsEventIndex++;
628 while (!stack.empty()) {
629 auto& [node, nextChild] = stack.back();
630 if (nextChild == InvalidNode) {
631 dfsIntervalCache_.exitIndex[node] = nextDfsEventIndex++;
632 stack.pop_back();
633 } else {
634 const NodeId child = nextChild;
635 nextChild = nextSibling_[child];
636 dfsIntervalCache_.entryIndex[child] = nextDfsEventIndex++;
637 stack.emplace_back(child, firstChild_[child]);
638 }
639 }
640
641 dfsIntervalCache_.valid = true;
642 }
643
647 inline void ensureDfsIntervalCache() const {
648 if (!dfsIntervalCache_.valid) {
649 recomputeDfsIntervalCache();
650 }
651 }
652
658 inline const LCAEulerRMQ& ensureLcaCache() const {
659 if (!lcaCache_) {
660 lcaCache_ = std::make_unique<LCAEulerRMQ>(this);
661 }
662 return *lcaCache_;
663 }
664
672 [[nodiscard]] inline std::optional<NodeId> lowestCommonAncestorFromDfsIntervalsEstablished(NodeId u, NodeId v) const {
673 if (u == v) {
674 return u;
675 }
676 ensureDfsIntervalCache();
677 if (dfsIntervalCache_.entryIndex[static_cast<std::size_t>(u)] < 0 ||
678 dfsIntervalCache_.entryIndex[static_cast<std::size_t>(v)] < 0) {
679 return InvalidNode;
680 }
681 const auto isAncestorCached = [&](NodeId ancestor, NodeId node) {
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)];
686 };
687 if (isAncestorCached(u, v)) {
688 return u;
689 }
690 if (isAncestorCached(v, u)) {
691 return v;
692 }
693 return std::nullopt;
694 }
695
702 [[nodiscard]] inline NodeId lowestCommonAncestorEstablished(NodeId u, NodeId v) const {
703 const std::optional<NodeId> intervalResult = lowestCommonAncestorFromDfsIntervalsEstablished(u, v);
704 return intervalResult.has_value() ? *intervalResult : ensureLcaCache().findLowestCommonAncestor(u, v);
705 }
706
711 [[nodiscard]] inline long double estimatedLcaRmqStorageBytes() const noexcept {
712 std::size_t rmqLevels = 1;
713 const std::size_t eulerLength = numNodes_ > 0 ? static_cast<std::size_t>(numNodes_) * 2 - 1 : 0;
715 blockSize *= 2) {
716 ++rmqLevels;
717 }
718 return static_cast<long double>(nodeParent_.size()) * static_cast<long double>(36 + 8 * rmqLevels);
719 }
720
733 template <typename QueryAccessor, typename ResultConsumer>
734 inline void forEachLowestCommonAncestorEstablished(std::size_t numQueries, QueryAccessor&& queryForIndex, ResultConsumer&& consumeLca,
735 bool forceTarjan = false) const {
736 if (numQueries == 0) {
737 return;
738 }
739
740 if (lcaCache_ && !forceTarjan) {
741 for (std::size_t queryIndex = 0; queryIndex < numQueries; ++queryIndex) {
742 const auto [first, second] = queryForIndex(queryIndex);
743 consumeLca(queryIndex, lowestCommonAncestorEstablished(first, second));
744 }
745 return;
746 }
747
748 const std::size_t numSlots = nodeParent_.size();
749 std::size_t numUnresolvedQueries = numQueries;
750 if (!forceTarjan) {
752 for (std::size_t queryIndex = 0; queryIndex < numQueries; ++queryIndex) {
753 const auto [first, second] = queryForIndex(queryIndex);
754 const std::optional<NodeId> intervalResult = lowestCommonAncestorFromDfsIntervalsEstablished(first, second);
755 if (intervalResult.has_value()) {
757 continue;
758 }
760 }
761 }
762 if (numUnresolvedQueries == 0 || rootNodeId_ == InvalidNode) {
763 return;
764 }
765
766 const long double estimatedRmqBytes = estimatedLcaRmqStorageBytes();
767 const long double estimatedTarjanBytes =
768 static_cast<long double>(numSlots) * 30.0L + static_cast<long double>(numUnresolvedQueries) * 16.0L;
769 const bool batchFitsCompactIndices = numQueries <= static_cast<std::size_t>(std::numeric_limits<std::uint32_t>::max() / 2);
771 for (std::size_t queryIndex = 0; queryIndex < numQueries; ++queryIndex) {
772 const auto [first, second] = queryForIndex(queryIndex);
773 if (lowestCommonAncestorFromDfsIntervalsEstablished(first, second).has_value()) {
774 continue;
775 }
776 consumeLca(queryIndex, ensureLcaCache().findLowestCommonAncestor(first, second));
777 }
778 return;
779 }
780
781 using QueryOffset = std::uint32_t;
782 std::vector<QueryOffset> queryOffsets(numSlots + 1, QueryOffset{0});
783 for (std::size_t queryIndex = 0; queryIndex < numQueries; ++queryIndex) {
784 const auto [first, second] = queryForIndex(queryIndex);
785 if (!forceTarjan && lowestCommonAncestorFromDfsIntervalsEstablished(first, second).has_value()) {
786 continue;
787 }
788 ++queryOffsets[static_cast<std::size_t>(first) + 1];
789 ++queryOffsets[static_cast<std::size_t>(second) + 1];
790 }
791 for (std::size_t index = 1; index < queryOffsets.size(); ++index) {
792 queryOffsets[index] += queryOffsets[index - 1];
793 }
794
795 struct QueryReference {
797 std::uint32_t queryIndex = 0;
798 };
799 std::vector<QueryReference> queryReferences(queryOffsets.back());
800 for (std::size_t queryIndex = 0; queryIndex < numQueries; ++queryIndex) {
801 const auto [first, second] = queryForIndex(queryIndex);
802 if (!forceTarjan && lowestCommonAncestorFromDfsIntervalsEstablished(first, second).has_value()) {
803 continue;
804 }
805 const QueryOffset firstPosition = queryOffsets[static_cast<std::size_t>(first)]++;
806 queryReferences[firstPosition] = {second, static_cast<std::uint32_t>(queryIndex)};
807 const QueryOffset secondPosition = queryOffsets[static_cast<std::size_t>(second)]++;
808 queryReferences[secondPosition] = {first, static_cast<std::uint32_t>(queryIndex)};
809 }
810 for (std::size_t index = numSlots; index > 0; --index) {
811 queryOffsets[index] = queryOffsets[index - 1];
812 }
814
815 const auto findSet = [](std::vector<NodeId>& setParents, NodeId node) {
816 NodeId representative = node;
817 while (setParents[static_cast<std::size_t>(representative)] != representative) {
818 representative = setParents[static_cast<std::size_t>(representative)];
819 }
820 while (setParents[static_cast<std::size_t>(node)] != node) {
821 const NodeId next = setParents[static_cast<std::size_t>(node)];
822 setParents[static_cast<std::size_t>(node)] = representative;
823 node = next;
824 }
825 return representative;
826 };
827 const auto uniteSets = [&](std::vector<NodeId>& setParents, std::vector<std::uint8_t>& setRanks, NodeId first, NodeId second) {
830 if (firstRoot == secondRoot) {
831 return firstRoot;
832 }
833 if (setRanks[static_cast<std::size_t>(firstRoot)] < setRanks[static_cast<std::size_t>(secondRoot)]) {
834 std::swap(firstRoot, secondRoot);
835 }
836 setParents[static_cast<std::size_t>(secondRoot)] = firstRoot;
837 if (setRanks[static_cast<std::size_t>(firstRoot)] == setRanks[static_cast<std::size_t>(secondRoot)]) {
838 ++setRanks[static_cast<std::size_t>(firstRoot)];
839 }
840 return firstRoot;
841 };
842
843 struct TraversalFrame {
844 NodeId node = InvalidNode;
846 };
847
848 std::vector<NodeId> setParents(numSlots, InvalidNode);
849 std::vector<std::uint8_t> setRanks(numSlots, std::uint8_t{0});
850 std::vector<NodeId> ancestors(numSlots, InvalidNode);
851 std::vector<std::uint8_t> finishedNodes(numSlots, std::uint8_t{0});
852 std::vector<TraversalFrame> traversalStack;
853 traversalStack.reserve(static_cast<std::size_t>(numNodes_));
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_)]});
857
858 while (!traversalStack.empty()) {
860 if (frame.nextChild != InvalidNode) {
861 const NodeId child = frame.nextChild;
862 frame.nextChild = nextSibling_[static_cast<std::size_t>(child)];
863 setParents[static_cast<std::size_t>(child)] = child;
864 ancestors[static_cast<std::size_t>(child)] = child;
865 traversalStack.push_back({child, firstChild_[static_cast<std::size_t>(child)]});
866 continue;
867 }
868
869 const NodeId node = frame.node;
870 const std::size_t nodeIndex = static_cast<std::size_t>(node);
871 finishedNodes[nodeIndex] = std::uint8_t{1};
874 if (finishedNodes[static_cast<std::size_t>(query.otherEndpoint)] != 0) {
875 const NodeId representative = findSet(setParents, query.otherEndpoint);
876 consumeLca(query.queryIndex, ancestors[static_cast<std::size_t>(representative)]);
877 }
878 }
879
880 traversalStack.pop_back();
881 if (!traversalStack.empty()) {
882 const NodeId parent = traversalStack.back().node;
883 const NodeId representative = uniteSets(setParents, setRanks, parent, node);
884 ancestors[static_cast<std::size_t>(representative)] = parent;
885 }
886 }
887 }
888
894 [[nodiscard]] inline std::vector<NodeId>
895 lowestCommonAncestorsEstablished(std::span<const std::pair<NodeId, NodeId>> queries) const {
896 std::vector<NodeId> lcas(queries.size(), InvalidNode);
897 forEachLowestCommonAncestorEstablished(
898 queries.size(), [&](std::size_t queryIndex) { return queries[queryIndex]; },
899 [&](std::size_t queryIndex, NodeId lca) { lcas[queryIndex] = lca; });
900 return lcas;
901 }
902
914 template <typename QueryAccessor, typename ResultConsumer>
915 inline void forEachGeneratedLowestCommonAncestorEstablished(std::size_t numQueries, QueryAccessor&& queryForIndex,
916 ResultConsumer&& consumeLca) const {
917 if (numQueries == 0) {
918 return;
919 }
920 if (lcaCache_) {
921 for (std::size_t queryIndex = 0; queryIndex < numQueries; ++queryIndex) {
922 const auto [first, second] = queryForIndex(queryIndex);
923 consumeLca(queryIndex, lowestCommonAncestorEstablished(first, second));
924 }
925 return;
926 }
927
928 const bool fullBatchFitsCompactIndices = numQueries <= static_cast<std::size_t>(std::numeric_limits<std::uint32_t>::max() / 2);
929 const long double estimatedFullBatchTarjanBytes =
930 static_cast<long double>(nodeParent_.size()) * 30.0L + static_cast<long double>(numQueries) * 24.0L;
931 if (fullBatchFitsCompactIndices && estimatedLcaRmqStorageBytes() > estimatedFullBatchTarjanBytes) {
932 constexpr std::size_t MaxStrategyProbes = 1024;
933 const std::size_t numProbes = std::min(numQueries, MaxStrategyProbes);
934 bool foundIncomparablePair = false;
935 for (std::size_t probe = 0; probe < numProbes; ++probe) {
936 const std::size_t queryIndex = probe * numQueries / numProbes;
937 const auto [first, second] = queryForIndex(queryIndex);
938 if (!lowestCommonAncestorFromDfsIntervalsEstablished(first, second).has_value()) {
940 break;
941 }
942 }
944 std::vector<std::pair<NodeId, NodeId>> allPairs(numQueries);
945 for (std::size_t queryIndex = 0; queryIndex < numQueries; ++queryIndex) {
947 }
948 forEachLowestCommonAncestorEstablished(
949 numQueries, [&](std::size_t queryIndex) { return allPairs[queryIndex]; }, std::forward<ResultConsumer>(consumeLca), true);
950 return;
951 }
952 }
953
954 std::size_t numUnresolvedQueries = 0;
955 for (std::size_t queryIndex = 0; queryIndex < numQueries; ++queryIndex) {
956 const auto [first, second] = queryForIndex(queryIndex);
957 const std::optional<NodeId> intervalResult = lowestCommonAncestorFromDfsIntervalsEstablished(first, second);
958 if (intervalResult.has_value()) {
960 } else {
962 }
963 }
964 if (numUnresolvedQueries == 0) {
965 return;
966 }
967
968 const bool filteredBatchFitsCompactIndices = numQueries <= static_cast<std::size_t>(std::numeric_limits<std::uint32_t>::max()) &&
970 static_cast<std::size_t>(std::numeric_limits<std::uint32_t>::max() / 2);
971 const long double estimatedFilteredTarjanBytes =
972 static_cast<long double>(nodeParent_.size()) * 30.0L + static_cast<long double>(numUnresolvedQueries) * 28.0L;
973 if (!filteredBatchFitsCompactIndices || estimatedLcaRmqStorageBytes() <= estimatedFilteredTarjanBytes) {
974 for (std::size_t queryIndex = 0; queryIndex < numQueries; ++queryIndex) {
975 const auto [first, second] = queryForIndex(queryIndex);
976 if (!lowestCommonAncestorFromDfsIntervalsEstablished(first, second).has_value()) {
977 consumeLca(queryIndex, ensureLcaCache().findLowestCommonAncestor(first, second));
978 }
979 }
980 return;
981 }
982
983 std::vector<std::pair<NodeId, NodeId>> unresolvedPairs;
984 std::vector<std::uint32_t> unresolvedQueryIndices;
987 for (std::size_t queryIndex = 0; queryIndex < numQueries; ++queryIndex) {
988 const auto query = queryForIndex(queryIndex);
989 if (!lowestCommonAncestorFromDfsIntervalsEstablished(query.first, query.second).has_value()) {
990 unresolvedPairs.push_back(query);
991 unresolvedQueryIndices.push_back(static_cast<std::uint32_t>(queryIndex));
992 }
993 }
994 forEachLowestCommonAncestorEstablished(
995 unresolvedPairs.size(), [&](std::size_t queryIndex) { return unresolvedPairs[queryIndex]; },
996 [&](std::size_t queryIndex, NodeId lca) { consumeLca(unresolvedQueryIndices[queryIndex], lca); }, true);
997 }
998
999 // ========================= Internal topology helpers ========================= //
1000 // These helpers mutate the dense node-indexed topology storage directly.
1007 void linkChildSlot(NodeId parentSlotId, NodeId childId) {
1008 if (parentSlotId < 0 || childId < 0)
1009 return;
1010
1011 // If the child already has a parent, detach it before reading sibling links.
1012 if (nodeParent_[childId] != InvalidNode) {
1013 unlinkChildSlot(nodeParent_[childId], childId);
1014 }
1015
1016 nodeParent_[childId] = parentSlotId;
1017 prevSibling_[childId] = lastChild_[parentSlotId];
1018 nextSibling_[childId] = InvalidNode;
1019
1020 if (firstChild_[parentSlotId] == InvalidNode) {
1021 firstChild_[parentSlotId] = lastChild_[parentSlotId] = childId;
1022 } else {
1023 nextSibling_[lastChild_[parentSlotId]] = childId;
1024 lastChild_[parentSlotId] = childId;
1025 }
1026 ++numChildrenByNode_[parentSlotId];
1027 bumpTopologyVersion();
1028 }
1029
1036 inline void unlinkChildSlot(NodeId parentSlotId, NodeId childId) {
1037 if (parentSlotId < 0 || childId < 0)
1038 return;
1039 if (nodeParent_[childId] != parentSlotId)
1040 return;
1041
1042 const NodeId prev = prevSibling_[childId];
1043 const NodeId next = nextSibling_[childId];
1044
1045 if (prev == InvalidNode)
1046 firstChild_[parentSlotId] = next;
1047 else
1048 nextSibling_[prev] = next;
1049
1050 if (next == InvalidNode)
1051 lastChild_[parentSlotId] = prev;
1052 else
1053 prevSibling_[next] = prev;
1054
1055 if (numChildrenByNode_[parentSlotId] > 0)
1056 --numChildrenByNode_[parentSlotId];
1057
1058 nodeParent_[childId] = InvalidNode;
1059 prevSibling_[childId] = InvalidNode;
1060 nextSibling_[childId] = InvalidNode;
1061 bumpTopologyVersion();
1062 }
1063
1071 inline void spliceChildrenSlots(NodeId toId, NodeId fromId, ChildSplicePolicy policy = ChildSplicePolicy::AppendToTargetTail) {
1072 if (toId < 0 || fromId < 0 || toId == fromId)
1073 return;
1074
1075 const NodeId firstFrom = firstChild_[fromId];
1076 const NodeId lastFrom = lastChild_[fromId];
1077 const int movedCount = numChildrenByNode_[fromId];
1078 const bool replaceSourceSlot = policy == ChildSplicePolicy::ReplaceSourceSlotWhenDirectChild && nodeParent_[fromId] == toId;
1079
1080 for (NodeId childId = firstFrom; childId != InvalidNode; childId = nextSibling_[childId]) {
1081 nodeParent_[childId] = toId;
1082 }
1083
1084 if (replaceSourceSlot) {
1085 const NodeId prev = prevSibling_[fromId];
1086 const NodeId next = nextSibling_[fromId];
1087
1088 if (firstFrom == InvalidNode) {
1089 if (prev == InvalidNode) {
1090 firstChild_[toId] = next;
1091 } else {
1092 nextSibling_[prev] = next;
1093 }
1094
1095 if (next == InvalidNode) {
1096 lastChild_[toId] = prev;
1097 } else {
1098 prevSibling_[next] = prev;
1099 }
1100 } else {
1101 if (prev == InvalidNode) {
1102 firstChild_[toId] = firstFrom;
1103 prevSibling_[firstFrom] = InvalidNode;
1104 } else {
1105 nextSibling_[prev] = firstFrom;
1106 prevSibling_[firstFrom] = prev;
1107 }
1108
1109 if (next == InvalidNode) {
1110 lastChild_[toId] = lastFrom;
1111 nextSibling_[lastFrom] = InvalidNode;
1112 } else {
1113 prevSibling_[next] = lastFrom;
1114 nextSibling_[lastFrom] = next;
1115 }
1116 }
1117
1118 nodeParent_[fromId] = InvalidNode;
1119 prevSibling_[fromId] = InvalidNode;
1120 nextSibling_[fromId] = InvalidNode;
1121 numChildrenByNode_[toId] += movedCount - 1;
1122 } else {
1123 if (firstFrom == InvalidNode)
1124 return;
1125
1126 if (firstChild_[toId] == InvalidNode) {
1127 firstChild_[toId] = firstFrom;
1128 lastChild_[toId] = lastFrom;
1129 } else {
1130 nextSibling_[lastChild_[toId]] = firstFrom;
1131 prevSibling_[firstFrom] = lastChild_[toId];
1132 lastChild_[toId] = lastFrom;
1133 }
1134
1135 numChildrenByNode_[toId] += movedCount;
1136 }
1137
1138 firstChild_[fromId] = InvalidNode;
1139 lastChild_[fromId] = InvalidNode;
1140 numChildrenByNode_[fromId] = 0;
1141 bumpTopologyVersion();
1142 }
1143
1149 inline void releaseNode(NodeId nodeId) {
1150 if (!isNode(nodeId) || !isAlive(nodeId) || isRoot(nodeId)) {
1151 return;
1152 }
1153 const NodeId nodeSlot = nodeId;
1154 if (nodeParent_[nodeSlot] != nodeSlot) {
1155 return;
1156 }
1157 if (numChildrenByNode_[nodeSlot] != 0 || properPartCardinalityByNode_[nodeSlot] != 0) {
1158 return;
1159 }
1160 releaseSlotNode(nodeSlot);
1161 }
1162
1168 inline NodeId allocateNode() {
1169 if (freeNodeIds_.empty()) {
1170 return InvalidNode;
1171 }
1172 const NodeId nodeSlot = allocateSlot();
1173 nodeParent_[nodeSlot] = nodeSlot;
1174 numNodes_++;
1175 invalidateDfsIntervalCache();
1176 bumpNodeStructureVersion();
1177 return nodeSlot;
1178 }
1179
1189 inline NodeId createDetachedNode() {
1190 const NodeId nodeSlot = allocateSlot();
1191 nodeParent_[static_cast<size_t>(nodeSlot)] = nodeSlot;
1192 numNodes_++;
1193 invalidateDfsIntervalCache();
1194 bumpNodeStructureVersion();
1195 return nodeSlot;
1196 }
1197
1205 inline void removeChild(NodeId parentNodeId, NodeId childId, bool releaseNodeFlag) {
1207 return;
1208 }
1210 const NodeId childSlotId = childId;
1211 if (releaseNodeFlag && numChildrenByNode_[static_cast<std::size_t>(childSlotId)] == 0 &&
1212 properPartCardinalityByNode_[static_cast<std::size_t>(childSlotId)] == 0) {
1213 freeNodeIds_.reserve(freeNodeIds_.size() + 1);
1214 }
1215 unlinkChildSlot(parentSlotId, childSlotId);
1216 nodeParent_[childSlotId] = childSlotId;
1217 invalidateDfsIntervalCache();
1218 if (releaseNodeFlag) {
1219 releaseNode(childId);
1220 }
1221 }
1222
1229 inline void attachNode(NodeId parentNodeId, NodeId nodeId) {
1231 return;
1232 }
1234 const NodeId nodeSlotId = nodeId;
1235 const NodeId oldParentSlotId = nodeParent_[nodeSlotId];
1237 unlinkChildSlot(oldParentSlotId, nodeSlotId);
1238 }
1239 nodeParent_[nodeSlotId] = InvalidNode;
1240 linkChildSlot(parentSlotId, nodeSlotId);
1241 }
1242
1248 inline void detachNode(NodeId nodeId) {
1249 if (!isAlive(nodeId) || isRoot(nodeId)) {
1250 return;
1251 }
1252 const NodeId nodeSlotId = nodeId;
1253 const NodeId parentSlotId = nodeParent_[nodeSlotId];
1255 return;
1256 }
1257 unlinkChildSlot(parentSlotId, nodeSlotId);
1258 nodeParent_[nodeSlotId] = nodeSlotId;
1259 }
1260
1267 inline void moveNode(NodeId nodeId, NodeId newParentId) {
1269 return;
1270 }
1271 const NodeId nodeSlotId = nodeId;
1273 const NodeId oldParentSlotId = nodeParent_[nodeSlotId];
1275 return;
1276 }
1278 unlinkChildSlot(oldParentSlotId, nodeSlotId);
1279 }
1280 nodeParent_[nodeSlotId] = InvalidNode;
1281 linkChildSlot(newParentSlotId, nodeSlotId);
1282 }
1283
1290 inline void moveChildren(NodeId parentNodeId, NodeId sourceId) {
1292 return;
1293 }
1294 spliceChildrenSlots(parentNodeId, sourceId);
1295 }
1296
1304 inline void movePixelToProperPart(NodeId targetNodeId, NodeId sourceNodeId, PixelId pixel) {
1306 return;
1307 }
1309 if (smallestNodeMap_[pixel] != sourceSlotId) {
1310 return;
1311 }
1312 unlinkPixelFromProperPart(sourceSlotId, pixel);
1313 appendDetachedProperPart(targetNodeId, pixel);
1314 bumpProperPartVersion();
1315 }
1316
1323 inline void mergeProperParts(NodeId targetNodeId, NodeId sourceNodeId) {
1325 return;
1326 }
1327 if (properHead_[static_cast<size_t>(sourceNodeId)] == InvalidPixel) {
1328 return;
1329 }
1330 spliceProperPartsSlots(targetNodeId, sourceNodeId);
1331 bumpProperPartVersion();
1332 }
1333
1339 inline void setRoot(NodeId nodeId) {
1340 if (!isAlive(nodeId)) {
1341 return;
1342 }
1343 const NodeId nodeSlot = nodeId;
1344 if (rootNodeId_ == nodeSlot) {
1345 return;
1346 }
1347 if (rootNodeId_ != InvalidNode && !isFreeSlot(rootNodeId_)) {
1348 nodeParent_[rootNodeId_] = rootNodeId_;
1349 prevSibling_[rootNodeId_] = InvalidNode;
1350 nextSibling_[rootNodeId_] = InvalidNode;
1351 }
1352 const NodeId oldParentSlot = nodeParent_[nodeSlot];
1354 unlinkChildSlot(oldParentSlot, nodeSlot);
1355 }
1356 rootNodeId_ = nodeSlot;
1357 nodeParent_[nodeSlot] = nodeSlot;
1358 prevSibling_[nodeSlot] = InvalidNode;
1359 nextSibling_[nodeSlot] = InvalidNode;
1360 bumpTopologyVersion();
1361 }
1362
1376 void initializeNativeTopologyStorage(std::vector<NodeId> nodeParent, std::vector<NodeId> smallestNodeMap, NodeId root,
1377 std::optional<GridDomain2D> gridDomain2D, MorphologicalTreeSemantics semantics) {
1378 if (gridDomain2D && smallestNodeMap.size() != gridDomain2D->size("Native topology 2D pixel domain")) {
1379 throw std::invalid_argument("Native topology pixel domain must match the attached 2D grid.");
1380 }
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.");
1383 }
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.");
1386 }
1387 switch (semantics.kind) {
1388 case MorphologicalTreeKind::Generic:
1389 case MorphologicalTreeKind::MaxTree:
1390 case MorphologicalTreeKind::MinTree:
1391 case MorphologicalTreeKind::TreeOfShapes:
1392 case MorphologicalTreeKind::UnrestrictedResidualTree:
1393 case MorphologicalTreeKind::SaturatedResidualTree:
1394 break;
1395 default:
1396 throw std::invalid_argument("Native topology kind is not supported.");
1397 }
1398 const int numNodeSlots = static_cast<int>(nodeParent.size());
1399 const std::size_t numPixels = smallestNodeMap.size();
1400 if (numNodeSlots <= 0) {
1401 throw std::invalid_argument("Native topology import requires at least one internal node.");
1402 }
1403 if (numPixels == 0) {
1404 throw std::invalid_argument("Native topology import requires at least one pixel.");
1405 }
1406 if (root < 0 || root >= numNodeSlots) {
1407 throw std::invalid_argument("Native topology import requires a valid root node id.");
1408 }
1410 semantics_ = std::move(semantics);
1411 gridDomain2D_ = gridDomain2D;
1412 const auto requireMatchingGrid = [this](const RegularGridAdjacency2D& adjacency, const char* context) {
1413 if (!gridDomain2D_) {
1414 throw std::invalid_argument(std::string(context) + " requires an attached 2D grid domain.");
1415 }
1416 if (adjacency.getNumRows() != gridDomain2D_->rows || adjacency.getNumColumns() != gridDomain2D_->columns) {
1417 throw std::invalid_argument(std::string(context) + " must match the attached 2D grid.");
1418 }
1419 };
1420 if (const auto* context = std::get_if<SharedAdjacencyContext>(&semantics_.constructionContext)) {
1421 requireMatchingGrid(context->adjacency, "SharedAdjacencyContext adjacency");
1422 } else if (const auto* context = std::get_if<SaturatedResidualContext>(&semantics_.constructionContext)) {
1423 requireMatchingGrid(context->adjacency, "SaturatedResidualContext adjacency");
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.");
1426 }
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.");
1430 }
1431 if (!gridDomain2D_) {
1432 throw std::invalid_argument("TopographicConvention requires an attached 2D grid domain.");
1433 }
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;
1436 const std::int64_t activeColumns = 2 * static_cast<std::int64_t>(gridDomain2D_->columns) + extension;
1437 if (activeRows <= 0 || activeColumns <= 0 || static_cast<std::int64_t>(convention->infinityPixel) >= activeRows * activeColumns) {
1438 throw std::invalid_argument("TopographicConvention infinity pixel must belong to the active topographic domain.");
1439 }
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");
1443 }
1444 }
1445
1446 initializeEmptyStorage(numPixels);
1447 nodeParent_ = std::move(nodeParent);
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);
1458 smallestNodeMap_ = std::move(smallestNodeMap);
1459 initializeProperPartStorage(numPixels);
1460
1461 rootNodeId_ = root;
1462 numNodes_ = static_cast<int>(numNodeSlots);
1463
1464 int selfParentedRoots = 0;
1465 for (NodeId nodeId = 0; nodeId < numNodeSlots; ++nodeId) {
1466 const NodeId parentId = nodeParent_[static_cast<size_t>(nodeId)];
1467 if (parentId == nodeId) {
1468 if (nodeId != root) {
1469 throw std::invalid_argument("Native topology import found a detached self-parented non-root node.");
1470 }
1472 continue;
1473 }
1474 if (parentId < 0 || parentId >= numNodeSlots) {
1475 throw std::invalid_argument("Native topology import found a parent outside the internal-node domain.");
1476 }
1477
1478 prevSibling_[static_cast<size_t>(nodeId)] = lastChild_[static_cast<size_t>(parentId)];
1479 if (lastChild_[static_cast<size_t>(parentId)] == InvalidNode) {
1480 firstChild_[static_cast<size_t>(parentId)] = nodeId;
1481 } else {
1482 nextSibling_[static_cast<size_t>(lastChild_[static_cast<size_t>(parentId)])] = nodeId;
1483 }
1484 lastChild_[static_cast<size_t>(parentId)] = nodeId;
1485 ++numChildrenByNode_[static_cast<size_t>(parentId)];
1486 }
1487 if (selfParentedRoots != 1) {
1488 throw std::invalid_argument("Native topology import must encode exactly one self-parented root.");
1489 }
1490
1491 rebuildProperPartLinksFromSmallestNodeMap();
1492 invalidateDfsIntervalCache();
1493 invalidateAllIterators();
1494 preservedExternalNodeIdOffset_.reset();
1495 }
1496
1506 void initializeNativeTopology(std::span<const NodeId> nodeParent, std::span<const NodeId> smallestNodeMap, NodeId root,
1507 std::optional<GridDomain2D> gridDomain2D, MorphologicalTreeSemantics semantics) {
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.");
1510 }
1511 const int numNodeSlots = static_cast<int>(nodeParent.size());
1512 for (NodeId smallestNodeId : smallestNodeMap) {
1513 if (smallestNodeId < 0 || smallestNodeId >= numNodeSlots) {
1514 throw std::invalid_argument("Native topology import found a smallest-node-map entry outside the internal-node domain.");
1515 }
1516 }
1517 initializeNativeTopologyStorage(std::vector<NodeId>(nodeParent.begin(), nodeParent.end()),
1518 std::vector<NodeId>(smallestNodeMap.begin(), smallestNodeMap.end()), root, gridDomain2D, std::move(semantics));
1520 }
1521
1533 void initializeValidatedNativeTopology(std::vector<NodeId>&& nodeParent, std::vector<NodeId>&& smallestNodeMap, NodeId root,
1534 std::optional<GridDomain2D> gridDomain2D, MorphologicalTreeSemantics semantics,
1535 detail::NativeTopologyProof&& topologyProof) {
1536 topologyProof.requireMatches(nodeParent.size(), smallestNodeMap.size(), root);
1537 initializeNativeTopologyStorage(std::move(nodeParent), std::move(smallestNodeMap), root, gridDomain2D, std::move(semantics));
1538#ifndef NDEBUG
1540#endif
1541 }
1542
1553 MorphologicalTree(detail::MorphologicalTreeConstructionTag, std::vector<NodeId>&& nodeParent, std::vector<NodeId>&& smallestNodeMap, NodeId root,
1554 std::optional<GridDomain2D> gridDomain2D, MorphologicalTreeSemantics semantics, detail::NativeTopologyProof&& topologyProof) {
1555 initializeValidatedNativeTopology(std::move(nodeParent), std::move(smallestNodeMap), root, gridDomain2D, std::move(semantics),
1556 std::move(topologyProof));
1557 }
1558
1562 MorphologicalTree() = default;
1563
1572 void moveCommittedStateFrom(MorphologicalTree&& other) {
1573 rootNodeId_ = std::exchange(other.rootNodeId_, InvalidNode);
1574 semantics_ = std::move(other.semantics_);
1575 other.semantics_ = MorphologicalTreeSemantics{};
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_;
1583
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_);
1598
1599 dfsIntervalCache_ = std::move(other.dfsIntervalCache_);
1600 other.dfsIntervalCache_ = {};
1601 lcaCache_.reset();
1602 other.lcaCache_.reset();
1603 nodeSupportMetadataCache_ = std::move(other.nodeSupportMetadataCache_);
1604 other.nodeSupportMetadataCache_ = {};
1605
1606 nodeStructureVersion_ = other.nodeStructureVersion_;
1607 topologyVersion_ = other.topologyVersion_;
1608 properPartVersion_ = other.properPartVersion_;
1609 mutationVersion_ = other.mutationVersion_;
1610
1611 // Iterators into the moved-from owner must not accidentally validate
1612 // merely because its previous token happened to be zero.
1613 ++other.nodeStructureVersion_;
1614 ++other.topologyVersion_;
1615 ++other.properPartVersion_;
1616 ++other.mutationVersion_;
1617 }
1618
1619 public:
1624
1631
1641 other.requireNotEditing("MorphologicalTree move construction");
1642 moveCommittedStateFrom(std::move(other));
1643 }
1644
1655 requireNotEditing("MorphologicalTree move assignment destination");
1656 other.requireNotEditing("MorphologicalTree move assignment source");
1657 if (this != &other) {
1658 const std::size_t previousNodeVersion = nodeStructureVersion_;
1659 const std::size_t previousTopologyVersion = topologyVersion_;
1660 const std::size_t previousProperPartVersion = properPartVersion_;
1661 const std::size_t previousMutationVersion = mutationVersion_;
1662 moveCommittedStateFrom(std::move(other));
1663 nodeStructureVersion_ = std::max(nodeStructureVersion_, previousNodeVersion) + 1;
1664 topologyVersion_ = std::max(topologyVersion_, previousTopologyVersion) + 1;
1665 properPartVersion_ = std::max(properPartVersion_, previousProperPartVersion) + 1;
1666 mutationVersion_ = std::max(mutationVersion_, previousMutationVersion) + 1;
1667 }
1668 return *this;
1669 }
1670
1674 virtual ~MorphologicalTree() = default;
1675
1694 MorphologicalTree(detail::MorphologicalTreeConstructionTag, std::span<const NodeId> nodeParent, std::span<const NodeId> smallestNodeMap, NodeId root,
1695 int rows, int columns, MorphologicalTreeSemantics semantics) {
1696 initializeNativeTopology(nodeParent, smallestNodeMap, root, GridDomain2D{rows, columns}, std::move(semantics));
1697 }
1698
1712 MorphologicalTree(detail::MorphologicalTreeConstructionTag, std::span<const NodeId> nodeParent, std::span<const NodeId> smallestNodeMap, NodeId root,
1714 initializeNativeTopology(nodeParent, smallestNodeMap, root, std::nullopt, std::move(semantics));
1715 }
1716
1729 requireNotEditing("MorphologicalTree::clone");
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_;
1745 cloned.alive_ = alive_;
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_;
1759 return cloned;
1760 }
1761
1762 // Forward declarations for nested iterator/range types whose definitions stay at the end of the class.
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;
1782
1783 public:
1784 // ========================= Public methods ========================= //
1785
1791 std::size_t getMutationVersion() const noexcept { return mutationVersion_; }
1792
1799 void requireMutationVersion(std::size_t expectedVersion, const char* context) const {
1800 MMCFILTERS_CONTRACT_REQUIRE(mutationVersion_ == expectedVersion,
1801 throw std::logic_error(std::string(context) + " cannot be used after the referenced tree topology has changed."));
1802 }
1803
1811 inline int numInternalNodeSlots() const { return static_cast<int>(nodeParent_.size()); }
1812
1818 inline int numPixels() const { return static_cast<int>(smallestNodeMap_.size()); }
1819
1829 inline int getNumHigraNodes() const {
1830 if (!preservedExternalNodeIdOffset_) {
1831 throw std::runtime_error("This tree does not preserve an imported Higra node-id space.");
1832 }
1833 return *preservedExternalNodeIdOffset_ + numInternalNodeSlots();
1834 }
1835
1845 inline int getNodeIdSpaceSize(NodeIdSpace outputSpace) const {
1846 switch (outputSpace) {
1847 case NodeIdSpace::MorphologicalTree:
1848 return numInternalNodeSlots();
1849 case NodeIdSpace::Higra:
1850 return getNumHigraNodes();
1851 }
1852 throw std::runtime_error("Unknown NodeIdSpace.");
1853 }
1854
1864 inline NodeId getHigraNodeId(NodeId nodeId) const noexcept {
1865 if (!preservedExternalNodeIdOffset_ || !isNode(nodeId) || !isAlive(nodeId)) {
1866 return InvalidNode;
1867 }
1868 return *preservedExternalNodeIdOffset_ + nodeId;
1869 }
1870
1876 inline NodeId root() const { return rootNodeId_; }
1877
1884 inline bool isNode(NodeId nodeId) const noexcept { return nodeId >= 0 && nodeId < static_cast<int>(nodeParent_.size()); }
1885
1892 inline bool isPixel(PixelId pixel) const noexcept { return pixel >= 0 && pixel < static_cast<int>(smallestNodeMap_.size()); }
1893
1900 inline bool isAlive(NodeId nodeId) const {
1901 if (!isNode(nodeId)) {
1902 return false;
1903 }
1904 const NodeId localId = nodeId;
1905 return localId >= 0 && localId < static_cast<NodeId>(nodeParent_.size()) && !isFreeSlot(localId) &&
1906 (nodeParent_[localId] != InvalidNode || localId == rootNodeId_);
1907 }
1908
1915 inline bool isRoot(NodeId nodeId) const { return nodeId == root(); }
1916
1922 inline int getNumFreeNodeSlots() const { return static_cast<int>(freeNodeIds_.size()); }
1923
1929 inline int numLeafNodes() const {
1930 int count = 0;
1931 for (NodeId nodeId : aliveNodeIds()) {
1932 if (isLeaf(nodeId)) {
1933 ++count;
1934 }
1935 }
1936 return count;
1937 }
1938
1945 inline int numChildren(NodeId nodeId) const {
1946 requireAliveNode(nodeId, "MorphologicalTree::numChildren");
1947 return numChildrenByNode_[nodeId];
1948 }
1949
1956 inline int numDescendants(NodeId nodeId) const {
1957 requireAliveNode(nodeId, "MorphologicalTree::numDescendants");
1958 ensureDfsIntervalCache();
1959 return (dfsIntervalCache_.exitIndex[nodeId] - dfsIntervalCache_.entryIndex[nodeId] - 1) / 2;
1960 }
1961
1968 inline int numSiblings(NodeId nodeId) const {
1969 requireAliveNode(nodeId, "MorphologicalTree::numSiblings");
1970 if (isRoot(nodeId)) {
1971 return 0;
1972 }
1974 return std::max(0, numChildren(parentNodeId) - 1);
1975 }
1976
1983 inline int dfsEntryIndex(NodeId nodeId) const {
1984 requireAliveNode(nodeId, "MorphologicalTree::dfsEntryIndex");
1985 ensureDfsIntervalCache();
1986 return dfsIntervalCache_.entryIndex[nodeId];
1987 }
1988
1995 inline int dfsExitIndex(NodeId nodeId) const {
1996 requireAliveNode(nodeId, "MorphologicalTree::dfsExitIndex");
1997 ensureDfsIntervalCache();
1998 return dfsIntervalCache_.exitIndex[nodeId];
1999 }
2000
2008 requireAliveNode(nodeId, "MorphologicalTree::getFirstChild");
2009 return firstChild_[nodeId];
2010 }
2011
2019 requireAliveNode(nodeId, "MorphologicalTree::getNextSibling");
2020 return nextSibling_[nodeId];
2021 }
2022
2029 inline bool isLeaf(NodeId nodeId) const { return getFirstChild(nodeId) == InvalidNode; }
2030
2038 requireAliveNode(nodeId, "MorphologicalTree::properPartCardinality");
2039 return properPartCardinalityByNode_[nodeId];
2040 }
2041
2048 inline bool hasEmptyProperPart(NodeId nodeId) const { return properPartCardinality(nodeId) == 0; }
2049
2058 requireAliveNode(parentNodeId, "MorphologicalTree::hasChild");
2059 requireAliveNode(childId, "MorphologicalTree::hasChild");
2060 return parent(childId) == parentNodeId;
2061 }
2062
2071 inline NodeId parent(NodeId nodeId) const {
2072 requireAliveNode(nodeId, "MorphologicalTree::parent");
2073 if (isRoot(nodeId)) {
2074 return nodeId;
2075 }
2076 const NodeId parentSlot = nodeParent_[nodeId];
2077 if (parentSlot == InvalidNode) {
2078 return InvalidNode;
2079 }
2080 if (parentSlot == nodeId) {
2081 return nodeId;
2082 }
2083 return parentSlot;
2084 }
2085
2092 inline NodeId smallestNode(PixelId pixel) const {
2093 if constexpr (contract::validationsEnabled) {
2094 return isPixel(pixel) ? smallestNodeMap_[static_cast<size_t>(pixel)] : InvalidNode;
2095 }
2096 return smallestNodeMap_[static_cast<size_t>(pixel)];
2097 }
2098
2104 [[nodiscard]] inline std::span<const NodeId> smallestNodeMap() const noexcept { return smallestNodeMap_; }
2105
2111 std::vector<NodeId> leaves() const {
2112 std::vector<NodeId> leaves;
2113 if (rootNodeId_ == InvalidNode) {
2114 return leaves;
2115 }
2117 s.push(this->rootNodeId_);
2118
2119 while (!s.empty()) {
2120 const NodeId id = s.pop();
2121 if (numChildrenByNode_[id] == 0) {
2122 leaves.push_back(id);
2123 } else {
2124 for (NodeId c = firstChild_[id]; c != InvalidNode; c = nextSibling_[c]) {
2125 s.push(c);
2126 }
2127 }
2128 }
2129 return leaves;
2130 }
2131
2140 inline MorphologicalTreeKind kind() const noexcept { return semantics_.kind; }
2141
2147 inline const MorphologicalTreeSemantics& semantics() const noexcept { return semantics_; }
2148
2155
2162
2165 return std::get_if<SharedAdjacencyContext>(&semantics_.constructionContext);
2166 }
2167
2170 return std::get_if<SaturatedResidualContext>(&semantics_.constructionContext);
2171 }
2172
2175 return std::get_if<TopographicConvention>(&semantics_.constructionContext);
2176 }
2177
2183 inline int numNodes() const noexcept { return numNodes_; }
2184
2190 inline bool isEditing() const noexcept { return editSessionOpen_; }
2191
2197 [[nodiscard]] const TreeEditValidationStatistics& getEditValidationStatistics() const noexcept { return editValidationStatistics_; }
2198
2204 inline void requireNotEditing(const char* context) const {
2206 throw std::logic_error(std::string(context) + " requires a committed MorphologicalTree; an edit session is still open."));
2207 }
2208
2219 if (numNodes_ == 0) {
2220 return false;
2221 }
2222 if (rootNodeId_ == InvalidNode || !isAlive(rootNodeId_)) {
2223 return true;
2224 }
2225
2226 for (NodeId nodeId = 0; nodeId < static_cast<NodeId>(nodeParent_.size()); ++nodeId) {
2227 if (!isAlive(nodeId) || nodeId == rootNodeId_) {
2228 continue;
2229 }
2230 if (nodeParent_[nodeId] == nodeId) {
2231 return true;
2232 }
2233 }
2234 return false;
2235 }
2236
2242 [[nodiscard]] inline bool hasGridDomain2D() const noexcept { return gridDomain2D_.has_value(); }
2243
2249 [[nodiscard]] inline const std::optional<GridDomain2D>& gridDomain2D() const noexcept { return gridDomain2D_; }
2250
2257 inline const GridDomain2D& requireGridDomain2D(const char* context) const {
2258 if (!gridDomain2D_) {
2259 throw std::invalid_argument(std::string(context) + " requires a regular 2D pixel domain.");
2260 }
2261 return *gridDomain2D_;
2262 }
2263
2269 inline int numRows() const { return requireGridDomain2D("MorphologicalTree::numRows").rows; }
2270
2276 inline int numColumns() const { return requireGridDomain2D("MorphologicalTree::numColumns").columns; }
2277
2286 if (numNodes_ <= 0) {
2287 throw std::runtime_error("Connected-tree validation requires at least one live node.");
2288 }
2289 if (!isAlive(rootNodeId_)) {
2290 throw std::runtime_error("Connected-tree validation requires a live root.");
2291 }
2292 if (nodeParent_[rootNodeId_] != rootNodeId_) {
2293 throw std::runtime_error("Connected-tree validation requires the root to point to itself.");
2294 }
2295
2296 const int numSlots = numInternalNodeSlots();
2297 const int domainPixelCount = numPixels();
2298 std::vector<int> expectedChildrenByNode(static_cast<size_t>(numSlots), 0);
2299 std::vector<int> expectedProperPartCardinalityByNode(static_cast<size_t>(numSlots), 0);
2300 int aliveCount = 0;
2301
2302 for (NodeId nodeId = 0; nodeId < numSlots; ++nodeId) {
2303 if (!isAlive(nodeId)) {
2304 continue;
2305 }
2306 ++aliveCount;
2307 const NodeId parentNodeId = nodeParent_[static_cast<size_t>(nodeId)];
2308 if (nodeId == rootNodeId_) {
2309 continue;
2310 }
2311 if (parentNodeId == InvalidNode) {
2312 throw std::runtime_error("Connected-tree validation found an alive node with no parent.");
2313 }
2314 if (parentNodeId == nodeId) {
2315 throw std::runtime_error("Connected-tree validation found a detached alive node.");
2316 }
2318 throw std::runtime_error("Connected-tree validation found an alive node whose parent is outside the alive node domain.");
2319 }
2320 expectedChildrenByNode[static_cast<size_t>(parentNodeId)] += 1;
2321 }
2322
2323 if (aliveCount != numNodes_) {
2324 throw std::runtime_error("Connected-tree validation found numNodes() out of sync with the alive node slots.");
2325 }
2326
2327 std::vector<uint8_t> seenAsChild(static_cast<size_t>(numSlots), 0);
2328 for (NodeId nodeId = 0; nodeId < numSlots; ++nodeId) {
2329 if (!isAlive(nodeId)) {
2330 continue;
2331 }
2332
2333 int actualChildCount = 0;
2335 for (NodeId childNodeId = firstChild_[static_cast<size_t>(nodeId)]; childNodeId != InvalidNode;
2336 childNodeId = nextSibling_[static_cast<size_t>(childNodeId)]) {
2338 throw std::runtime_error("Connected-tree validation found a child list that references a non-alive node.");
2339 }
2340 if (nodeParent_[static_cast<size_t>(childNodeId)] != nodeId) {
2341 throw std::runtime_error("Connected-tree validation found a child whose parent pointer disagrees with the child list.");
2342 }
2343 if (prevSibling_[static_cast<size_t>(childNodeId)] != previousChildId) {
2344 throw std::runtime_error("Connected-tree validation found broken previous-sibling links.");
2345 }
2346 if (seenAsChild[static_cast<size_t>(childNodeId)] != 0) {
2347 throw std::runtime_error("Connected-tree validation found a node referenced by multiple child lists.");
2348 }
2349 seenAsChild[static_cast<size_t>(childNodeId)] = 1;
2353 throw std::runtime_error("Connected-tree validation found a cycle in a child list.");
2354 }
2355 }
2356
2357 if (firstChild_[static_cast<size_t>(nodeId)] == InvalidNode) {
2358 if (lastChild_[static_cast<size_t>(nodeId)] != InvalidNode) {
2359 throw std::runtime_error("Connected-tree validation found an empty child list with a non-empty tail pointer.");
2360 }
2361 } else {
2362 if (lastChild_[static_cast<size_t>(nodeId)] != previousChildId) {
2363 throw std::runtime_error("Connected-tree validation found an incorrect last-child pointer.");
2364 }
2365 if (nextSibling_[static_cast<size_t>(previousChildId)] != InvalidNode) {
2366 throw std::runtime_error("Connected-tree validation found a child list whose tail still points to a next sibling.");
2367 }
2368 }
2369
2370 if (actualChildCount != numChildrenByNode_[static_cast<size_t>(nodeId)]) {
2371 throw std::runtime_error("Connected-tree validation found an incorrect child count cache.");
2372 }
2373 if (actualChildCount != expectedChildrenByNode[static_cast<size_t>(nodeId)]) {
2374 throw std::runtime_error("Connected-tree validation found child lists out of sync with parent pointers.");
2375 }
2376 }
2377
2378 if (seenAsChild[static_cast<size_t>(rootNodeId_)] != 0) {
2379 throw std::runtime_error("Connected-tree validation found the root inside a child list.");
2380 }
2381 for (NodeId nodeId = 0; nodeId < numSlots; ++nodeId) {
2382 if (!isAlive(nodeId) || nodeId == rootNodeId_) {
2383 continue;
2384 }
2385 if (seenAsChild[static_cast<size_t>(nodeId)] == 0) {
2386 throw std::runtime_error("Connected-tree validation found a non-root alive node missing from the child lists.");
2387 }
2388 }
2389
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) {
2394 const NodeId nodeId = traversal[index];
2395 for (NodeId childId = firstChild_[static_cast<size_t>(nodeId)]; childId != InvalidNode; childId = nextSibling_[static_cast<size_t>(childId)]) {
2396 traversal.push_back(childId);
2397 }
2398 }
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.");
2401 }
2402
2403 for (PixelId pixel = 0; pixel < domainPixelCount; ++pixel) {
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.");
2407 }
2408 expectedProperPartCardinalityByNode[static_cast<size_t>(smallestNodeId)] += 1;
2409 }
2410
2411 std::vector<uint8_t> seenProperPart(static_cast<size_t>(domainPixelCount), 0);
2412 for (NodeId nodeId = 0; nodeId < numSlots; ++nodeId) {
2413 if (!isAlive(nodeId)) {
2414 continue;
2415 }
2416
2419 for (PixelId pixel = properHead_[static_cast<size_t>(nodeId)]; pixel != InvalidPixel;
2420 pixel = nextProperPart_[static_cast<size_t>(pixel)]) {
2421 if (!isPixel(pixel)) {
2422 throw std::runtime_error("Connected-tree validation found an invalid pixel id in a proper-part list.");
2423 }
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.");
2426 }
2427 if (prevProperPart_[static_cast<size_t>(pixel)] != previousProperPartId) {
2428 throw std::runtime_error("Connected-tree validation found broken previous-proper-part links.");
2429 }
2430 if (seenProperPart[static_cast<size_t>(pixel)] != 0) {
2431 throw std::runtime_error("Connected-tree validation found a proper part referenced multiple times.");
2432 }
2433 seenProperPart[static_cast<size_t>(pixel)] = 1;
2434 previousProperPartId = pixel;
2437 throw std::runtime_error("Connected-tree validation found a cycle in a proper-part list.");
2438 }
2439 }
2440
2441 if (properHead_[static_cast<size_t>(nodeId)] == InvalidPixel) {
2442 if (properTail_[static_cast<size_t>(nodeId)] != InvalidPixel) {
2443 throw std::runtime_error("Connected-tree validation found an empty proper-part list with a non-empty tail pointer.");
2444 }
2445 } else {
2446 if (properTail_[static_cast<size_t>(nodeId)] != previousProperPartId) {
2447 throw std::runtime_error("Connected-tree validation found an incorrect proper-part tail pointer.");
2448 }
2449 if (nextProperPart_[static_cast<size_t>(previousProperPartId)] != InvalidPixel) {
2450 throw std::runtime_error("Connected-tree validation found a proper-part list whose tail still points forward.");
2451 }
2452 }
2453
2454 if (actualProperPartCardinality != properPartCardinalityByNode_[static_cast<size_t>(nodeId)]) {
2455 throw std::runtime_error("Connected-tree validation found an incorrect direct proper-part count cache.");
2456 }
2458 throw std::runtime_error("Connected-tree validation found proper-part lists out of sync with the smallest-node map.");
2459 }
2460 }
2461
2462 for (PixelId pixel = 0; pixel < domainPixelCount; ++pixel) {
2463 if (seenProperPart[static_cast<size_t>(pixel)] == 0) {
2464 throw std::runtime_error("Connected-tree validation found a pixel missing from the proper-part lists.");
2465 }
2466 }
2467
2469 for (auto it = traversal.rbegin(); it != traversal.rend(); ++it) {
2470 const NodeId nodeId = *it;
2471 if (subtreeSupport[static_cast<size_t>(nodeId)] == 0) {
2472 throw std::runtime_error("Connected-tree validation found a live node whose subtree support is empty.");
2473 }
2474 if (nodeId != rootNodeId_) {
2475 const NodeId parentId = nodeParent_[static_cast<size_t>(nodeId)];
2476 subtreeSupport[static_cast<size_t>(parentId)] += subtreeSupport[static_cast<size_t>(nodeId)];
2477 }
2478 }
2479 }
2480
2487 try {
2489 return {true, ""};
2490 } catch (const std::exception& ex) {
2491 return {false, ex.what()};
2492 } catch (...) {
2493 return {false, "Connected-tree validation failed with an unknown error."};
2494 }
2495 }
2496
2504 if (numNodes_ <= 0) {
2505 return false;
2506 }
2507 for (NodeId nodeId = 0; nodeId < numInternalNodeSlots(); ++nodeId) {
2508 if (isAlive(nodeId) && properPartCardinalityByNode_[static_cast<std::size_t>(nodeId)] == 0) {
2509 return false;
2510 }
2511 }
2512 return true;
2513 }
2514
2522 for (NodeId nodeId = 0; nodeId < numInternalNodeSlots(); ++nodeId) {
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) + ".");
2525 }
2526 }
2527 }
2528
2536 inline bool isAncestor(NodeId u, NodeId v) const {
2537 requireAliveNode(u, "MorphologicalTree::isAncestor");
2538 requireAliveNode(v, "MorphologicalTree::isAncestor");
2539 ensureDfsIntervalCache();
2540 const NodeId slotU = u;
2541 const NodeId slotV = v;
2542 return dfsIntervalCache_.entryIndex[slotU] <= dfsIntervalCache_.entryIndex[slotV] &&
2543 dfsIntervalCache_.exitIndex[slotU] >= dfsIntervalCache_.exitIndex[slotV];
2544 }
2545
2553 inline bool isDescendant(NodeId u, NodeId v) const {
2554 requireAliveNode(u, "MorphologicalTree::isDescendant");
2555 requireAliveNode(v, "MorphologicalTree::isDescendant");
2556 ensureDfsIntervalCache();
2557 const NodeId slotU = u;
2558 const NodeId slotV = v;
2559 return dfsIntervalCache_.entryIndex[slotV] <= dfsIntervalCache_.entryIndex[slotU] &&
2560 dfsIntervalCache_.exitIndex[slotV] >= dfsIntervalCache_.exitIndex[slotU];
2561 }
2569 inline bool isComparable(NodeId u, NodeId v) const { return isAncestor(u, v) || isAncestor(v, u); }
2570
2578 inline bool isStrictAncestor(NodeId u, NodeId v) const { return u != v && isAncestor(u, v); }
2579
2587 inline bool isStrictDescendant(NodeId u, NodeId v) const { return u != v && isDescendant(u, v); }
2588
2596 inline bool isStrictComparable(NodeId u, NodeId v) const { return isStrictAncestor(u, v) || isStrictAncestor(v, u); }
2597
2613 if (!isAlive(u) || !isAlive(v)) {
2614 return InvalidNode;
2615 }
2616 return lowestCommonAncestorEstablished(u, v);
2617 }
2618
2630 [[nodiscard]] inline std::vector<NodeId>
2631 lowestCommonAncestors(std::span<const std::pair<NodeId, NodeId>> queries) const {
2632 std::vector<std::pair<NodeId, NodeId>> liveQueries;
2633 std::vector<std::size_t> liveQueryIndices;
2634 std::vector<NodeId> lcas(queries.size(), InvalidNode);
2635 liveQueries.reserve(queries.size());
2636 liveQueryIndices.reserve(queries.size());
2637 for (std::size_t queryIndex = 0; queryIndex < queries.size(); ++queryIndex) {
2638 const auto [first, second] = queries[queryIndex];
2639 if (!isAlive(first) || !isAlive(second)) {
2640 continue;
2641 }
2642 liveQueries.emplace_back(first, second);
2643 liveQueryIndices.push_back(queryIndex);
2644 }
2645
2646 const std::vector<NodeId> liveLcas = lowestCommonAncestorsEstablished(liveQueries);
2647 for (std::size_t liveIndex = 0; liveIndex < liveLcas.size(); ++liveIndex) {
2649 }
2650 return lcas;
2651 }
2652
2658 inline AliveNodeRange aliveNodeIds() const { return AliveNodeRange(this, 0, numInternalNodeSlots(), nodeStructureVersion_); }
2659
2667 requireAliveNode(nodeId, "MorphologicalTree::children");
2668 return ChildrenRange(this, firstChild_[nodeId], topologyVersion_);
2669 }
2670
2678 requireAliveNode(nodeId, "MorphologicalTree::properPart");
2679 return ProperPartRange(this, properHead_[nodeId], properPartVersion_);
2680 }
2681
2692 requireAliveNode(nodeId, "MorphologicalTree::nodeSupport");
2693 return NodeSupportRange(this, nodeId, topologyVersion_, properPartVersion_);
2694 }
2695
2701 inline PostOrderNodeRange postOrder() const { return PostOrderNodeRange(this, root(), topologyVersion_); }
2702
2710 requireAliveNode(rootNodeId, "MorphologicalTree::postOrder");
2711 return PostOrderNodeRange(this, rootNodeId, topologyVersion_);
2712 }
2713
2719 inline BreadthFirstNodeRange breadthFirstTraversal() const { return BreadthFirstNodeRange(this, root(), topologyVersion_); }
2720
2728 requireAliveNode(rootNodeId, "MorphologicalTree::breadthFirstTraversal");
2729 return BreadthFirstNodeRange(this, rootNodeId, topologyVersion_);
2730 }
2731
2742 requireAliveNode(nodeId, "MorphologicalTree::ancestors");
2743 return AncestorNodeRange(this, nodeId, topologyVersion_);
2744 }
2745
2754 requireAliveNode(sourceNodeId, "MorphologicalTree::getPathBetweenNodes");
2755 requireAliveNode(targetNodeId, "MorphologicalTree::getPathBetweenNodes");
2756 return PathBetweenNodesRange(this, sourceNodeId, targetNodeId, topologyVersion_);
2757 }
2758
2766 requireAliveNode(nodeId, "MorphologicalTree::subtreeNodes");
2767 return SubtreeNodeRange(this, nodeId, topologyVersion_);
2768 }
2769
2777 requireAliveNode(nodeId, "MorphologicalTree::descendants");
2778 return DescendantNodeRange(this, nodeId, topologyVersion_);
2779 }
2780
2794
2804 inline void pruneNode(NodeId nodeId) {
2805 requireNotEditing("MorphologicalTree::pruneNode");
2806 requireAliveNonRootNode(nodeId, "MorphologicalTree::pruneNode");
2809 throw std::invalid_argument("MorphologicalTree::pruneNode requires an attached non-root node.");
2810 }
2812 std::size_t releaseCount = 0;
2814 ++releaseCount;
2815 }
2816 freeNodeIds_.reserve(freeNodeIds_.size() + releaseCount);
2817
2818 const auto descendToDeepestLastChild = [this](NodeId startId) {
2820 while (firstChild_[currentId] != InvalidNode) {
2821 currentId = lastChild_[currentId];
2822 }
2823 return currentId;
2824 };
2825
2827 while (true) {
2828 const NodeId currentParentSlotId = nodeParent_[currentId];
2829 mergeProperParts(parentSlotId, currentId);
2831 unlinkChildSlot(currentParentSlotId, currentId);
2832 }
2833 nodeParent_[currentId] = currentId;
2834 releaseNode(currentId);
2835
2836 if (currentId == nodeId) {
2837 break;
2838 }
2840 }
2841 }
2842
2853 requireNotEditing("MorphologicalTree::mergeNodeIntoParent");
2854 requireAliveNonRootNode(nodeId, "MorphologicalTree::mergeNodeIntoParent");
2857 throw std::invalid_argument("MorphologicalTree::mergeNodeIntoParent requires an attached non-root node.");
2858 }
2860 const NodeId nodeSlotId = nodeId;
2861 freeNodeIds_.reserve(freeNodeIds_.size() + 1);
2862
2863 spliceProperPartsSlots(parentSlotId, nodeSlotId);
2864 spliceChildrenSlots(parentSlotId, nodeSlotId, ChildSplicePolicy::ReplaceSourceSlotWhenDirectChild);
2865 nodeParent_[nodeSlotId] = nodeSlotId;
2866 bumpProperPartVersion();
2867 releaseNode(nodeId);
2868 }
2869
2870 private:
2871 // ========================= Internal classes ========================= //
2911 class LCAEulerRMQ {
2912 private:
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;
2926 const MorphologicalTree* tree_ = nullptr;
2927
2934 void depthFirstTraversal(NodeId nodeId, int currentDepth) {
2935 std::vector<std::pair<NodeId, NodeId>> stack;
2936 stack.emplace_back(nodeId, tree_->firstChild_[nodeId]);
2937 firstOccurrence_[static_cast<size_t>(nodeId)] = static_cast<int>(euler_.size());
2938 euler_.push_back(nodeId);
2939 depth_.push_back(currentDepth);
2940 while (!stack.empty()) {
2941 const NodeId child = stack.back().second;
2942 if (child == InvalidNode) {
2943 stack.pop_back();
2944 if (!stack.empty()) {
2945 euler_.push_back(stack.back().first);
2946 depth_.push_back(currentDepth + static_cast<int>(stack.size()) - 1);
2947 }
2948 } else {
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]);
2954 }
2955 }
2956 }
2957
2961 void buildSparseTable() {
2962 const int n = static_cast<int>(depth_.size());
2963 if (n == 0) {
2964 log2_.clear();
2965 sparseTable_.clear();
2966 sparseTableStride_ = 0;
2967 return;
2968 }
2969
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;
2973 }
2974
2975 sparseTableStride_ = 1;
2976 while ((1 << sparseTableStride_) <= n) {
2977 ++sparseTableStride_;
2978 }
2979
2980 sparseTable_.assign(static_cast<size_t>(n) * static_cast<size_t>(sparseTableStride_), 0);
2981
2982 for (int i = 0; i < n; ++i) {
2983 sparseTable_[sparseTableIndex(i, 0)] = i;
2984 }
2985
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;
2994 }
2995 }
2996 }
2997
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);
3007 }
3008
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;
3022 }
3023
3024 public:
3030 explicit LCAEulerRMQ(const MorphologicalTree* tree) : tree_(tree) {
3031 if (tree_ == nullptr || tree_->root() == InvalidNode) {
3032 return;
3033 }
3034
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)));
3037 firstOccurrence_.assign(static_cast<size_t>(tree_->numInternalNodeSlots()), -1);
3038
3039 depthFirstTraversal(tree_->root(), 0);
3040 buildSparseTable();
3041 }
3042
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) {
3054 return InvalidNode;
3055 }
3056
3057 int left = firstU;
3058 int right = firstV;
3059 if (left > right) {
3060 std::swap(left, right);
3061 }
3062 return euler_[static_cast<size_t>(rmq(left, right))];
3063 }
3064 };
3065
3066 public:
3067 // ========================= Internal classes and iterators ========================= //
3068
3073 private:
3075 const MorphologicalTree* T_ = nullptr;
3077 NodeId current_ = InvalidNode;
3079 NodeId end_ = InvalidNode;
3081 std::size_t expectedVersion_ = 0;
3082
3086 void settle_() {
3087 if (!T_) {
3088 current_ = InvalidNode;
3089 return;
3090 }
3091 T_->checkNodeIteratorVersion(expectedVersion_);
3092 while (current_ != InvalidNode && current_ < end_ && !T_->isAlive(current_)) {
3093 ++current_;
3094 }
3095 if (current_ >= end_) {
3096 current_ = InvalidNode;
3097 }
3098 }
3099
3100 public:
3102 using iterator_category = std::input_iterator_tag;
3106 using difference_type = std::ptrdiff_t;
3108 using pointer = const NodeId*;
3110 using reference = const NodeId&;
3111
3116
3126 : T_(tree), current_(current), end_(end), expectedVersion_(expectedVersion) {
3127 settle_();
3128 }
3129
3136 T_->checkNodeIteratorVersion(expectedVersion_);
3137 if (current_ != InvalidNode) {
3138 ++current_;
3139 settle_();
3140 }
3141 return *this;
3142 }
3143
3150 T_->checkNodeIteratorVersion(expectedVersion_);
3151 return current_;
3152 }
3153
3160 bool operator==(const AliveNodeIterator& other) const { return current_ == other.current_; }
3161
3168 bool operator!=(const AliveNodeIterator& other) const { return !(*this == other); }
3169 };
3170
3175 private:
3177 const MorphologicalTree* T_ = nullptr;
3179 NodeId begin_ = InvalidNode;
3181 NodeId end_ = InvalidNode;
3183 std::size_t expectedVersion_ = 0;
3184
3185 public:
3189 AliveNodeRange() = default;
3190
3200 : T_(tree), begin_(begin), end_(end), expectedVersion_(expectedVersion) {}
3201
3207 AliveNodeIterator begin() const { return AliveNodeIterator(T_, begin_, end_, expectedVersion_); }
3208
3214 AliveNodeIterator end() const { return AliveNodeIterator(T_, InvalidNode, end_, expectedVersion_); }
3215 };
3216
3221 private:
3223 const MorphologicalTree* T_ = nullptr;
3225 NodeId currentLocal_ = InvalidNode;
3227 std::size_t expectedVersion_ = 0;
3228
3229 public:
3231 using iterator_category = std::forward_iterator_tag;
3235 using difference_type = std::ptrdiff_t;
3237 using pointer = const NodeId*;
3239 using reference = const NodeId&;
3240
3244 ChildrenIterator() = default;
3245
3254 : T_(tree), currentLocal_(currentLocal), expectedVersion_(expectedVersion) {}
3255
3262 T_->checkTopologyIteratorVersion(expectedVersion_);
3263 if (T_ && currentLocal_ != InvalidNode) {
3264 currentLocal_ = T_->nextSibling_[currentLocal_];
3265 }
3266 return *this;
3267 }
3268
3275 T_->checkTopologyIteratorVersion(expectedVersion_);
3276 return currentLocal_;
3277 }
3278
3285 bool operator==(const ChildrenIterator& other) const { return currentLocal_ == other.currentLocal_; }
3286
3293 bool operator!=(const ChildrenIterator& other) const { return !(*this == other); }
3294 };
3295
3300 private:
3302 const MorphologicalTree* T_ = nullptr;
3304 NodeId firstLocal_ = InvalidNode;
3306 std::size_t expectedVersion_ = 0;
3307
3308 public:
3312 ChildrenRange() = default;
3313
3322 : T_(tree), firstLocal_(firstLocal), expectedVersion_(expectedVersion) {}
3323
3329 ChildrenIterator begin() const { return ChildrenIterator(T_, firstLocal_, expectedVersion_); }
3330
3336 ChildrenIterator end() const { return ChildrenIterator(T_, InvalidNode, expectedVersion_); }
3337 };
3338
3343 private:
3345 const MorphologicalTree* T_ = nullptr;
3347 PixelId currentProperPart_ = InvalidPixel;
3349 std::size_t expectedVersion_ = 0;
3350
3351 public:
3353 using iterator_category = std::forward_iterator_tag;
3357 using difference_type = std::ptrdiff_t;
3359 using pointer = const PixelId*;
3361 using reference = const PixelId&;
3362
3367
3376 : T_(tree), currentProperPart_(currentProperPart), expectedVersion_(expectedVersion) {}
3377
3384 T_->checkProperPartIteratorVersion(expectedVersion_);
3385 if (T_ && currentProperPart_ != InvalidPixel) {
3386 currentProperPart_ = T_->nextProperPart_[currentProperPart_];
3387 }
3388 return *this;
3389 }
3390
3397 T_->checkProperPartIteratorVersion(expectedVersion_);
3398 return currentProperPart_;
3399 }
3400
3407 bool operator==(const ProperPartIterator& other) const { return currentProperPart_ == other.currentProperPart_; }
3408
3415 bool operator!=(const ProperPartIterator& other) const { return !(*this == other); }
3416 };
3417
3422 private:
3424 const MorphologicalTree* T_ = nullptr;
3426 PixelId firstProperPart_ = InvalidPixel;
3428 std::size_t expectedVersion_ = 0;
3429
3430 public:
3434 ProperPartRange() = default;
3435
3444 : T_(tree), firstProperPart_(firstProperPart), expectedVersion_(expectedVersion) {}
3445
3451 ProperPartIterator begin() const { return ProperPartIterator(T_, firstProperPart_, expectedVersion_); }
3452
3458 ProperPartIterator end() const { return ProperPartIterator(T_, InvalidPixel, expectedVersion_); }
3459 };
3460
3465 private:
3467 const MorphologicalTree* T_ = nullptr;
3469 std::vector<NodeId> nodeStack_;
3471 PixelId currentProperPart_ = InvalidPixel;
3473 std::size_t expectedTopologyVersion_ = 0;
3475 std::size_t expectedProperPartVersion_ = 0;
3476
3480 void checkVersions() const {
3481 T_->checkTopologyIteratorVersion(expectedTopologyVersion_);
3482 T_->checkProperPartIteratorVersion(expectedProperPartVersion_);
3483 }
3484
3490 void pushChildren(NodeId nodeId) {
3491 std::vector<NodeId> children;
3492 for (NodeId childId : T_->children(nodeId)) {
3493 children.push_back(childId);
3494 }
3495 for (auto it = children.rbegin(); it != children.rend(); ++it) {
3496 nodeStack_.push_back(*it);
3497 }
3498 }
3499
3503 void settle() {
3504 checkVersions();
3505 while (currentProperPart_ == InvalidPixel && !nodeStack_.empty()) {
3506 const NodeId nodeId = nodeStack_.back();
3507 nodeStack_.pop_back();
3508 pushChildren(nodeId);
3509 currentProperPart_ = T_->properHead_[static_cast<size_t>(nodeId)];
3510 }
3511 }
3512
3513 public:
3515 using iterator_category = std::forward_iterator_tag;
3519 using difference_type = std::ptrdiff_t;
3521 using pointer = const PixelId*;
3523 using reference = const PixelId&;
3524
3529
3539 : T_(tree), expectedTopologyVersion_(expectedTopologyVersion), expectedProperPartVersion_(expectedProperPartVersion) {
3540 if (T_ && rootNodeId != InvalidNode) {
3541 nodeStack_.push_back(rootNodeId);
3542 settle();
3543 }
3544 }
3545
3552 checkVersions();
3553 if (currentProperPart_ != InvalidPixel) {
3554 currentProperPart_ = T_->nextProperPart_[static_cast<size_t>(currentProperPart_)];
3555 }
3556 settle();
3557 return *this;
3558 }
3559
3566 checkVersions();
3567 return currentProperPart_;
3568 }
3569
3576 bool operator==(const NodeSupportIterator& other) const { return currentProperPart_ == other.currentProperPart_; }
3577
3584 bool operator!=(const NodeSupportIterator& other) const { return !(*this == other); }
3585 };
3586
3591 private:
3593 const MorphologicalTree* T_ = nullptr;
3595 NodeId rootNodeId_ = InvalidNode;
3597 std::size_t expectedTopologyVersion_ = 0;
3599 std::size_t expectedProperPartVersion_ = 0;
3600
3601 public:
3605 NodeSupportRange() = default;
3606
3616 : T_(tree), rootNodeId_(rootNodeId), expectedTopologyVersion_(expectedTopologyVersion), expectedProperPartVersion_(expectedProperPartVersion) {}
3617
3623 NodeSupportIterator begin() const { return NodeSupportIterator(T_, rootNodeId_, expectedTopologyVersion_, expectedProperPartVersion_); }
3624
3631 };
3632
3637 private:
3639 struct Item {
3641 NodeId id;
3643 bool expanded;
3644 };
3646 const MorphologicalTree* T_ = nullptr;
3648 std::vector<Item> stack_;
3650 NodeId current_ = InvalidNode;
3652 std::size_t expectedVersion_ = 0;
3653
3657 void settle_() {
3658 T_->checkTopologyIteratorVersion(expectedVersion_);
3659 while (!stack_.empty()) {
3660 Item& top = stack_.back();
3661 if (!top.expanded) {
3662 top.expanded = true;
3663 // Reverse sibling links preserve child order without a temporary vector.
3664 for (NodeId child = T_->lastChild_[top.id]; child != InvalidNode; child = T_->prevSibling_[child]) {
3665 stack_.push_back({child, false});
3666 }
3667 } else {
3668 current_ = top.id;
3669 return;
3670 }
3671 }
3672 current_ = InvalidNode;
3673 }
3674
3675 public:
3677 using iterator_category = std::input_iterator_tag;
3681 using difference_type = std::ptrdiff_t;
3683 using pointer = const NodeId*;
3685 using reference = const NodeId&;
3686
3691
3699 PostOrderNodeIterator(const MorphologicalTree* tree, NodeId rootNodeId, std::size_t expectedVersion) : T_(tree), expectedVersion_(expectedVersion) {
3700 if (T_ && rootNodeId != InvalidNode) {
3701 stack_.push_back({rootNodeId, false});
3702 settle_();
3703 }
3704 }
3705
3712 T_->checkTopologyIteratorVersion(expectedVersion_);
3713 if (!stack_.empty()) {
3714 stack_.pop_back();
3715 settle_();
3716 }
3717 return *this;
3718 }
3719
3726 T_->checkTopologyIteratorVersion(expectedVersion_);
3727 return current_;
3728 }
3729
3736 bool operator==(const PostOrderNodeIterator& other) const { return current_ == other.current_; }
3737
3744 bool operator!=(const PostOrderNodeIterator& other) const { return !(*this == other); }
3745 };
3746
3751 private:
3753 const MorphologicalTree* T_ = nullptr;
3755 NodeId rootNodeId_ = InvalidNode;
3757 std::size_t expectedVersion_ = 0;
3758
3759 public:
3764
3773 : T_(tree), rootNodeId_(rootNodeId), expectedVersion_(expectedVersion) {}
3774
3780 PostOrderNodeIterator begin() const { return PostOrderNodeIterator(T_, rootNodeId_, expectedVersion_); }
3781
3788 };
3789
3794 private:
3796 const MorphologicalTree* T_ = nullptr;
3798 FastQueue<NodeId> queue_;
3800 std::size_t expectedVersion_ = 0;
3801
3802 public:
3804 using iterator_category = std::input_iterator_tag;
3808 using difference_type = std::ptrdiff_t;
3810 using pointer = const NodeId*;
3812 using reference = const NodeId&;
3813
3818
3826 BreadthFirstNodeIterator(const MorphologicalTree* tree, NodeId rootNodeId, std::size_t expectedVersion) : T_(tree), expectedVersion_(expectedVersion) {
3827 if (T_ && rootNodeId != InvalidNode) {
3828 queue_.push(rootNodeId);
3829 }
3830 }
3831
3838 T_->checkTopologyIteratorVersion(expectedVersion_);
3839 if (!queue_.empty()) {
3840 NodeId current = queue_.pop();
3841 for (NodeId childId : T_->children(current)) {
3842 queue_.push(childId);
3843 }
3844 }
3845 return *this;
3846 }
3847
3854 T_->checkTopologyIteratorVersion(expectedVersion_);
3855 return queue_.front();
3856 }
3857
3864 bool operator==(const BreadthFirstNodeIterator& other) const { return queue_.empty() == other.queue_.empty(); }
3865
3872 bool operator!=(const BreadthFirstNodeIterator& other) const { return !(*this == other); }
3873 };
3874
3879 private:
3881 const MorphologicalTree* T_ = nullptr;
3883 NodeId rootNodeId_ = InvalidNode;
3885 std::size_t expectedVersion_ = 0;
3886
3887 public:
3892
3901 : T_(tree), rootNodeId_(rootNodeId), expectedVersion_(expectedVersion) {}
3902
3908 BreadthFirstNodeIterator begin() const { return BreadthFirstNodeIterator(T_, rootNodeId_, expectedVersion_); }
3909
3916 };
3917
3922 private:
3924 const MorphologicalTree* T_ = nullptr;
3926 NodeId current_ = InvalidNode;
3928 std::size_t expectedVersion_ = 0;
3929
3930 public:
3932 using iterator_category = std::input_iterator_tag;
3936 using difference_type = std::ptrdiff_t;
3938 using pointer = const NodeId*;
3940 using reference = const NodeId&;
3941
3946
3955 : T_(tree), current_(current), expectedVersion_(expectedVersion) {}
3956
3963 T_->checkTopologyIteratorVersion(expectedVersion_);
3964 if (T_ && current_ != InvalidNode) {
3965 if (T_->isRoot(current_)) {
3966 current_ = InvalidNode;
3967 } else {
3968 current_ = T_->parent(current_);
3969 }
3970 }
3971 return *this;
3972 }
3973
3980 T_->checkTopologyIteratorVersion(expectedVersion_);
3981 return current_;
3982 }
3983
3990 bool operator==(const AncestorNodeIterator& other) const { return current_ == other.current_; }
3991
3998 bool operator!=(const AncestorNodeIterator& other) const { return !(*this == other); }
3999 };
4000
4005 private:
4007 const MorphologicalTree* T_ = nullptr;
4009 NodeId start_ = InvalidNode;
4011 std::size_t expectedVersion_ = 0;
4012
4013 public:
4018
4027 : T_(tree), start_(start), expectedVersion_(expectedVersion) {}
4028
4034 AncestorNodeIterator begin() const { return AncestorNodeIterator(T_, start_, expectedVersion_); }
4035
4042 };
4043
4048 private:
4050 const MorphologicalTree* T_ = nullptr;
4052 const std::vector<NodeId>* path_ = nullptr;
4054 std::size_t index_ = 0;
4056 std::size_t expectedVersion_ = 0;
4057
4058 public:
4060 using iterator_category = std::input_iterator_tag;
4064 using difference_type = std::ptrdiff_t;
4066 using pointer = const NodeId*;
4068 using reference = const NodeId&;
4069
4074
4083 PathBetweenNodesIterator(const MorphologicalTree* tree, const std::vector<NodeId>* path, std::size_t index, std::size_t expectedVersion)
4084 : T_(tree), path_(path), index_(index), expectedVersion_(expectedVersion) {}
4085
4092 T_->checkTopologyIteratorVersion(expectedVersion_);
4093 if (path_ && index_ < path_->size()) {
4094 ++index_;
4095 }
4096 return *this;
4097 }
4098
4105 T_->checkTopologyIteratorVersion(expectedVersion_);
4106 return (*path_)[index_];
4107 }
4108
4115 bool operator==(const PathBetweenNodesIterator& other) const { return path_ == other.path_ && index_ == other.index_; }
4116
4123 bool operator!=(const PathBetweenNodesIterator& other) const { return !(*this == other); }
4124 };
4125
4130 private:
4132 const MorphologicalTree* T_ = nullptr;
4134 std::vector<NodeId> path_;
4136 std::size_t expectedVersion_ = 0;
4137
4146 static std::vector<NodeId> buildPath(const MorphologicalTree* tree, NodeId sourceNodeId, NodeId targetNodeId) {
4147 if (tree == nullptr || !tree->isAlive(sourceNodeId) || !tree->isAlive(targetNodeId)) {
4148 return {};
4149 }
4150
4151 auto componentAnchor = [tree](NodeId nodeId) {
4153 while (true) {
4154 const NodeId parentNodeId = tree->parent(currentNodeId);
4156 return currentNodeId;
4157 }
4159 }
4160 };
4161
4163 return {};
4164 }
4165
4167 if (lcaNodeId == InvalidNode) {
4168 return {};
4169 }
4170
4171 std::vector<NodeId> path;
4173 path.push_back(currentNodeId);
4174 if (currentNodeId == lcaNodeId) {
4175 break;
4176 }
4177
4178 const NodeId parentNodeId = tree->parent(currentNodeId);
4180 return {};
4181 }
4182 }
4183
4184 std::vector<NodeId> descendingTail;
4186 descendingTail.push_back(currentNodeId);
4187
4188 const NodeId parentNodeId = tree->parent(currentNodeId);
4190 return {};
4191 }
4192 }
4193
4194 path.insert(path.end(), descendingTail.rbegin(), descendingTail.rend());
4195 return path;
4196 }
4197
4198 public:
4203
4213 : T_(tree), path_(buildPath(tree, sourceNodeId, targetNodeId)), expectedVersion_(expectedVersion) {}
4214
4220 PathBetweenNodesIterator begin() const { return PathBetweenNodesIterator(T_, &path_, 0, expectedVersion_); }
4221
4227 PathBetweenNodesIterator end() const { return PathBetweenNodesIterator(T_, &path_, path_.size(), expectedVersion_); }
4228 };
4229
4234 private:
4236 const MorphologicalTree* T_ = nullptr;
4238 std::vector<NodeId> stack_;
4240 std::size_t expectedVersion_ = 0;
4241
4242 public:
4244 using iterator_category = std::input_iterator_tag;
4248 using difference_type = std::ptrdiff_t;
4250 using pointer = const NodeId*;
4252 using reference = const NodeId&;
4253
4258
4266 SubtreeNodeIterator(const MorphologicalTree* tree, NodeId rootNodeId, std::size_t expectedVersion) : T_(tree), expectedVersion_(expectedVersion) {
4267 if (T_ && rootNodeId != InvalidNode) {
4268 stack_.push_back(rootNodeId);
4269 }
4270 }
4271
4278 T_->checkTopologyIteratorVersion(expectedVersion_);
4279 if (!stack_.empty()) {
4280 NodeId current = stack_.back();
4281 stack_.pop_back();
4282 // Push in reverse sibling order so the first child is visited next.
4283 for (NodeId child = T_->lastChild_[current]; child != InvalidNode; child = T_->prevSibling_[child]) {
4284 stack_.push_back(child);
4285 }
4286 }
4287 return *this;
4288 }
4289
4296 T_->checkTopologyIteratorVersion(expectedVersion_);
4297 return stack_.back();
4298 }
4299
4306 bool operator==(const SubtreeNodeIterator& other) const { return stack_.empty() == other.stack_.empty(); }
4307
4314 bool operator!=(const SubtreeNodeIterator& other) const { return !(*this == other); }
4315 };
4316
4321 private:
4323 const MorphologicalTree* T_ = nullptr;
4325 NodeId rootNodeId_ = InvalidNode;
4327 std::size_t expectedVersion_ = 0;
4328
4329 public:
4333 SubtreeNodeRange() = default;
4334
4343 : T_(tree), rootNodeId_(rootNodeId), expectedVersion_(expectedVersion) {}
4344
4350 SubtreeNodeIterator begin() const { return SubtreeNodeIterator(T_, rootNodeId_, expectedVersion_); }
4351
4358 };
4359
4364 private:
4366 const MorphologicalTree* T_ = nullptr;
4368 NodeId rootNodeId_ = InvalidNode;
4370 std::size_t expectedVersion_ = 0;
4371
4372 public:
4377
4386 : T_(tree), rootNodeId_(rootNodeId), expectedVersion_(expectedVersion) {}
4387
4394 auto it = SubtreeNodeIterator(T_, rootNodeId_, expectedVersion_);
4395 ++it;
4396 return it;
4397 }
4398
4405 };
4406};
4407
4408} // namespace mmcfilters
int PixelId
Pixel identifier type used by source and active construction domains.
Definition Common.hpp:26
int NodeId
Node identifier type used throughout the project.
Definition Common.hpp:17
constexpr NodeId InvalidNode
Sentinel value used to denote an invalid node identifier.
Definition Common.hpp:34
constexpr PixelId InvalidPixel
Sentinel value used to denote an invalid pixel identifier.
Definition Common.hpp:43
#define MMCFILTERS_CONTRACT_REQUIRE(condition,...)
Evaluates a caller precondition and its failure action only in checked builds.
Definition Contract.hpp:53
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.
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.
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.