MorphologicalAttributeFilters
Public API documentation
Loading...
Searching...
No Matches
MorphologicalTree.hpp
1#pragma once
2
3#include "../utils/AdjacencyRelation.hpp"
4#include "../utils/Assert.hpp"
5#include "../utils/Altitude.hpp"
6#include "../utils/Image.hpp"
7#include "../utils/Common.hpp"
8#include "../dataStructure/FastQueue.hpp"
9#include "BuilderMorphologicalTreeByUnionFind.hpp"
10#include "detail/MorphologicalTreeConstructionTag.hpp"
11#include <algorithm>
12#include <cassert>
13#include <cstddef>
14#include <cstdint>
15#include <functional>
16#include <limits>
17#include <list>
18#include <memory>
19#include <optional>
20#include <set>
21#include <span>
22#include <stdexcept>
23#include <string>
24#include <tuple>
25#include <unordered_map>
26#include <unordered_set>
27#include <utility>
28#include <vector>
29
30namespace mmcfilters {
31
35enum class NodeIdSpace {
36 MORPHOLOGICAL_TREE,
37 HIGRA
38};
39
47enum class ComponentTreeKind {
48 MAX_TREE,
49 MIN_TREE
50};
51
58enum class MorphologicalTreeKind {
59 MAX_TREE,
60 MIN_TREE,
61 TREE_OF_SHAPES,
62 SELF_DUAL_RESIDUAL_TREE
63};
64
74 bool ok = false;
75
77 std::string message;
78
82 explicit operator bool() const noexcept {
83 return ok;
84 }
85};
86
87// Forward declaration for the edit-session wrapper.
88class TreeEditor;
89
121private:
122 friend class TreeEditor;
123
124 class LCAEulerRMQ; // Forward declaration for the LCA cache implementation.
125
126 // ========================= Private attributes ========================= //
127 NodeId rootNodeId_ = InvalidNode;
128 MorphologicalTreeKind treeType_ = MorphologicalTreeKind::MAX_TREE;
129 int numRows_ = 0;
130 int numCols_ = 0;
131 std::optional<AdjacencyRelation> adj_; // optional adjacency context; not part of the structural tree contract
132 std::optional<std::pair<AdjacencyRelation, AdjacencyRelation>> tosAdjacencyPolicy_; // min-tree adjacency, max-tree adjacency
133 int numNodes_ = 0;
134 bool preservesHigraNodeIdSpace_ = false;
135 bool editSessionOpen_ = false;
136
137 // Proper-part ownership, indexed by proper-part global id [0, getNumTotalProperParts()).
138 std::vector<NodeId> properPartOwner_;
139
140 // Parent links, indexed by local node-slot id [0, getNumInternalNodeSlots()).
141 std::vector<NodeId> nodeParent_;
142
143 // Internal hierarchy linked structure, indexed by local node-slot id [0, getNumInternalNodeSlots()).
144 std::vector<NodeId> firstChild_;
145 std::vector<NodeId> nextSibling_;
146 std::vector<NodeId> prevSibling_;
147 std::vector<NodeId> lastChild_;
148 std::vector<int> numChildrenByNode_;
149
150 // Free slot management and alive-node iteration, indexed by local node-slot id [0, getNumInternalNodeSlots()).
151 std::vector<uint8_t> alive_;
152 std::vector<NodeId> freeNodeIds_;
153
154 // Proper-parts linked lists, indexed by local node-slot id [0, getNumTotalProperParts()).
155 std::vector<NodeId> properHead_;
156 std::vector<NodeId> properTail_;
157 std::vector<int> numProperPartsByNode_;
158 std::vector<NodeId> nextProperPart_;
159 std::vector<NodeId> prevProperPart_;
160
161 // Structural caches for traversal-based queries, indexed by local node-slot id [0, getNumInternalNodeSlots()).
162 struct PrePostOrderCache {
163 std::vector<int> timePreOrder;
164 std::vector<int> timePostOrder;
165 bool valid = false;
166
170 void invalidate() noexcept { valid = false; }
171 };
172 mutable PrePostOrderCache prePostOrderCache_;
173 mutable std::unique_ptr<LCAEulerRMQ> lcaCache_;
174
175
176 // Version counters for iterator invalidation.
177 std::size_t nodeStructureVersion_ = 0;
178 std::size_t topologyVersion_ = 0;
179 std::size_t properPartVersion_ = 0;
180 std::size_t mutationVersion_ = 0;
181
182 enum class ChildSplicePolicy {
183 AppendToTargetTail,
184 ReplaceSourceSlotWhenDirectChild
185 };
186
187 // ========================= Private methods ========================= //
191 inline NodeId makeNode(NodeId parentSlotId) {
192 const NodeId id = this->allocateSlot();
193
194 if (parentSlotId >= 0) {
195 linkChildSlot(parentSlotId, id);
196 } else {
197 nodeParent_[id] = id;
198 }
199 this->numNodes_++;
200 return id;
201 }
202
206 inline NodeId allocateSlot() {
207 if (!freeNodeIds_.empty()) {
208 const NodeId slotId = freeNodeIds_.back();
209 freeNodeIds_.pop_back();
210
211 nodeParent_[slotId] = InvalidNode;
212 firstChild_[slotId] = InvalidNode;
213 nextSibling_[slotId] = InvalidNode;
214 prevSibling_[slotId] = InvalidNode;
215 lastChild_[slotId] = InvalidNode;
216 numChildrenByNode_[slotId] = 0;
217 alive_[slotId] = 1;
218 properHead_[slotId] = InvalidNode;
219 properTail_[slotId] = InvalidNode;
220 numProperPartsByNode_[slotId] = 0;
221 return slotId;
222 }
223
224 const NodeId slotId = static_cast<NodeId>(nodeParent_.size());
225 nodeParent_.push_back(InvalidNode);
226 firstChild_.push_back(InvalidNode);
227 nextSibling_.push_back(InvalidNode);
228 prevSibling_.push_back(InvalidNode);
229 lastChild_.push_back(InvalidNode);
230 numChildrenByNode_.push_back(0);
231 alive_.push_back(1);
232 properHead_.push_back(InvalidNode);
233 properTail_.push_back(InvalidNode);
234 numProperPartsByNode_.push_back(0);
235 return slotId;
236 }
237
241 inline void reserveNodes(int expected) {
242 nodeParent_.reserve(expected);
243 firstChild_.reserve(expected);
244 nextSibling_.reserve(expected);
245 prevSibling_.reserve(expected);
246 lastChild_.reserve(expected);
247 numChildrenByNode_.reserve(expected);
248 alive_.reserve(expected);
249 properHead_.reserve(expected);
250 properTail_.reserve(expected);
251 numProperPartsByNode_.reserve(expected);
252 }
253
257 inline void initializeProperPartStorage(size_t numProperParts) {
258 nextProperPart_.assign(numProperParts, InvalidNode);
259 prevProperPart_.assign(numProperParts, InvalidNode);
260 }
261
265 inline void invalidateHigraNodeIdSpace() noexcept { preservesHigraNodeIdSpace_ = false; }
266
270 inline void beginEditSession() {
271 if (editSessionOpen_) {
272 throw std::logic_error("A MorphologicalTree edit session is already open.");
273 }
274 editSessionOpen_ = true;
275 }
276
280 inline void endEditSession() noexcept {
281 editSessionOpen_ = false;
282 }
283
287 inline void initializeEmptyStorage(size_t numProperParts) {
288 rootNodeId_ = InvalidNode;
289 numNodes_ = 0;
290 properPartOwner_.assign(numProperParts, InvalidNode);
291
292 nodeParent_.clear();
293 firstChild_.clear();
294 nextSibling_.clear();
295 prevSibling_.clear();
296 lastChild_.clear();
297 numChildrenByNode_.clear();
298 alive_.clear();
299 freeNodeIds_.clear();
300 properHead_.clear();
301 properTail_.clear();
302 numProperPartsByNode_.clear();
303 initializeProperPartStorage(numProperParts);
304 invalidateHigraNodeIdSpace();
305
306 prePostOrderCache_.timePreOrder.clear();
307 prePostOrderCache_.timePostOrder.clear();
308 invalidatePrePostOrderCache();
309 invalidateAllIterators();
310 }
311
315 inline void releaseSlotStorage(NodeId slotId) noexcept {
316 nodeParent_[slotId] = InvalidNode;
317 firstChild_[slotId] = InvalidNode;
318 nextSibling_[slotId] = InvalidNode;
319 prevSibling_[slotId] = InvalidNode;
320 lastChild_[slotId] = InvalidNode;
321 numChildrenByNode_[slotId] = 0;
322 alive_[slotId] = 0;
323 properHead_[slotId] = InvalidNode;
324 properTail_[slotId] = InvalidNode;
325 numProperPartsByNode_[slotId] = 0;
326 freeNodeIds_.push_back(slotId);
327 }
328
332 inline bool isFreeSlot(NodeId slotId) const noexcept {
333 return slotId >= 0 && slotId < static_cast<NodeId>(alive_.size()) && alive_[slotId] == 0;
334 }
335
339 template<class PixelType>
340 static void requireNonEmptyImageDomain(const ImagePtr<PixelType>& img, const char* context) {
341 if (!img) {
342 throw std::invalid_argument(std::string(context) + " requires a non-null image.");
343 }
344 if (img->getNumRows() <= 0 || img->getNumCols() <= 0 || img->getSize() <= 0) {
345 throw std::invalid_argument(std::string(context) + " requires a non-empty 2D image.");
346 }
347 }
348
352 static std::pair<AdjacencyRelation, AdjacencyRelation> treeOfShapesAdjacencyPolicy(ToSInterpolation interpolation, int rows, int cols) {
353 switch (interpolation) {
354 case ToSInterpolation::SelfDual:
355 return {AdjacencyRelation(rows, cols, 1.0), AdjacencyRelation(rows, cols, 1.0)};
356 case ToSInterpolation::Min4cMax8c:
357 return {AdjacencyRelation(rows, cols, 1.0), AdjacencyRelation(rows, cols, 1.5)};
358 case ToSInterpolation::Min8cMax4c:
359 return {AdjacencyRelation(rows, cols, 1.5), AdjacencyRelation(rows, cols, 1.0)};
360 }
361 throw std::invalid_argument("Unsupported tree-of-shapes interpolation.");
362 }
363
367 inline void requireAliveNode(NodeId nodeId, const char* context) const {
368 if (!isAlive(nodeId)) {
369 throw std::invalid_argument(std::string(context) + " requires a live internal NodeId.");
370 }
371 }
372
376 inline void requireAliveNonRootNode(NodeId nodeId, const char* context) const {
377 requireAliveNode(nodeId, context);
378 if (isRoot(nodeId)) {
379 throw std::invalid_argument(std::string(context) + " cannot target the root node.");
380 }
381 }
382
386 inline void rebuildProperPartLinksFromOwnership() {
387 properHead_.assign(nodeParent_.size(), InvalidNode);
388 properTail_.assign(nodeParent_.size(), InvalidNode);
389 numProperPartsByNode_.assign(nodeParent_.size(), 0);
390 initializeProperPartStorage(properPartOwner_.size());
391
392 for (NodeId pixelId = 0; pixelId < static_cast<NodeId>(properPartOwner_.size()); ++pixelId) {
393 const NodeId ownerSlotId = properPartOwner_[pixelId];
394 if (ownerSlotId == InvalidNode || ownerSlotId >= static_cast<NodeId>(nodeParent_.size()) || isFreeSlot(ownerSlotId)) {
395 continue;
396 }
397
398 const NodeId tailPixelId = properTail_[ownerSlotId];
399 if (tailPixelId == InvalidNode) {
400 properHead_[ownerSlotId] = pixelId;
401 properTail_[ownerSlotId] = pixelId;
402 } else {
403 nextProperPart_[tailPixelId] = pixelId;
404 prevProperPart_[pixelId] = tailPixelId;
405 properTail_[ownerSlotId] = pixelId;
406 }
407 numProperPartsByNode_[ownerSlotId]++;
408 }
409 }
410
414 inline void unlinkProperPartFromOwner(NodeId ownerSlotId, NodeId pixelId) noexcept {
415 const NodeId prev = prevProperPart_[static_cast<size_t>(pixelId)];
416 const NodeId next = nextProperPart_[static_cast<size_t>(pixelId)];
417
418 if (prev == InvalidNode) {
419 properHead_[static_cast<size_t>(ownerSlotId)] = next;
420 } else {
421 nextProperPart_[static_cast<size_t>(prev)] = next;
422 }
423
424 if (next == InvalidNode) {
425 properTail_[static_cast<size_t>(ownerSlotId)] = prev;
426 } else {
427 prevProperPart_[static_cast<size_t>(next)] = prev;
428 }
429
430 nextProperPart_[static_cast<size_t>(pixelId)] = InvalidNode;
431 prevProperPart_[static_cast<size_t>(pixelId)] = InvalidNode;
432 --numProperPartsByNode_[static_cast<size_t>(ownerSlotId)];
433 }
434
438 inline void appendDetachedProperPart(NodeId targetSlotId, NodeId pixelId) noexcept {
439 properPartOwner_[static_cast<size_t>(pixelId)] = targetSlotId;
440 const NodeId tail = properTail_[static_cast<size_t>(targetSlotId)];
441 if (tail == InvalidNode) {
442 properHead_[static_cast<size_t>(targetSlotId)] = pixelId;
443 properTail_[static_cast<size_t>(targetSlotId)] = pixelId;
444 } else {
445 nextProperPart_[static_cast<size_t>(tail)] = pixelId;
446 prevProperPart_[static_cast<size_t>(pixelId)] = tail;
447 properTail_[static_cast<size_t>(targetSlotId)] = pixelId;
448 }
449 ++numProperPartsByNode_[static_cast<size_t>(targetSlotId)];
450 }
451
455 inline void spliceProperPartsSlots(NodeId targetSlotId, NodeId sourceSlotId) noexcept {
456 const NodeId sourceHead = properHead_[static_cast<size_t>(sourceSlotId)];
457 if (sourceHead == InvalidNode) {
458 return;
459 }
460
461 for (NodeId pixelId = sourceHead; pixelId != InvalidNode; pixelId = nextProperPart_[static_cast<size_t>(pixelId)]) {
462 properPartOwner_[static_cast<size_t>(pixelId)] = targetSlotId;
463 }
464
465 const NodeId sourceTail = properTail_[static_cast<size_t>(sourceSlotId)];
466 const int sourceCount = numProperPartsByNode_[static_cast<size_t>(sourceSlotId)];
467 const NodeId targetTail = properTail_[static_cast<size_t>(targetSlotId)];
468
469 if (targetTail == InvalidNode) {
470 properHead_[static_cast<size_t>(targetSlotId)] = sourceHead;
471 properTail_[static_cast<size_t>(targetSlotId)] = sourceTail;
472 } else {
473 nextProperPart_[static_cast<size_t>(targetTail)] = sourceHead;
474 prevProperPart_[static_cast<size_t>(sourceHead)] = targetTail;
475 properTail_[static_cast<size_t>(targetSlotId)] = sourceTail;
476 }
477
478 numProperPartsByNode_[static_cast<size_t>(targetSlotId)] += sourceCount;
479 properHead_[static_cast<size_t>(sourceSlotId)] = InvalidNode;
480 properTail_[static_cast<size_t>(sourceSlotId)] = InvalidNode;
481 numProperPartsByNode_[static_cast<size_t>(sourceSlotId)] = 0;
482 }
483
487 inline void releaseSlotNode(NodeId slotNodeId) noexcept {
488 releaseSlotStorage(slotNodeId);
489 numNodes_--;
490 invalidatePrePostOrderCache();
491 bumpNodeStructureVersion();
492 }
493
497 inline void invalidateAllIterators() noexcept {
498 nodeStructureVersion_ = 0;
499 topologyVersion_ = 0;
500 properPartVersion_ = 0;
501 ++mutationVersion_;
502 lcaCache_.reset();
503 }
504
508 inline void invalidateLcaCache() const noexcept { lcaCache_.reset(); }
509
513 inline void bumpNodeStructureVersion() noexcept {
514 ++nodeStructureVersion_;
515 ++mutationVersion_;
516 invalidateLcaCache();
517 invalidateHigraNodeIdSpace();
518 }
519
523 inline void bumpTopologyVersion() noexcept {
524 ++topologyVersion_;
525 ++mutationVersion_;
526 invalidateLcaCache();
527 invalidatePrePostOrderCache();
528 invalidateHigraNodeIdSpace();
529 }
530
534 inline void bumpProperPartVersion() noexcept {
535 ++properPartVersion_;
536 ++mutationVersion_;
537 invalidateHigraNodeIdSpace();
538 }
539
543 inline void checkNodeIteratorVersion([[maybe_unused]] std::size_t expectedVersion) const {
544 assert(expectedVersion == nodeStructureVersion_ && "Alive-node iterator invalidated by node-structure mutation.");
545 }
546
550 inline void checkTopologyIteratorVersion([[maybe_unused]] std::size_t expectedVersion) const {
551 assert(expectedVersion == topologyVersion_ && "Topology iterator invalidated by tree-structure mutation.");
552 }
553
557 inline void checkProperPartIteratorVersion([[maybe_unused]] std::size_t expectedVersion) const {
558 assert(expectedVersion == properPartVersion_ && "Proper-parts iterator invalidated by proper-part mutation.");
559 }
560
564 inline void invalidatePrePostOrderCache() const noexcept { prePostOrderCache_.invalidate(); }
565
569 inline void recomputePrePostOrderCache() const {
570 prePostOrderCache_.timePreOrder.assign(nodeParent_.size(), -1);
571 prePostOrderCache_.timePostOrder.assign(nodeParent_.size(), -1);
572
573 if (rootNodeId_ == InvalidNode) {
574 prePostOrderCache_.valid = true;
575 return;
576 }
577
578 int timer = 0;
579 computeIncrementalAttributes(
580 const_cast<MorphologicalTree*>(this),
581 getRoot(),
582 [&](NodeId nodeId) -> void {
583 prePostOrderCache_.timePreOrder[nodeId] = timer++;
584 },
585 [&](NodeId, NodeId) -> void {},
586 [&](NodeId nodeId) -> void {
587 prePostOrderCache_.timePostOrder[nodeId] = timer++;
588 }
589 );
590
591 prePostOrderCache_.valid = true;
592 }
593
597 inline void ensurePrePostOrderCache() const {
598 if (!prePostOrderCache_.valid) {
599 recomputePrePostOrderCache();
600 }
601 }
602
606 inline const LCAEulerRMQ& ensureLcaCache() const {
607 if (!lcaCache_) {
608 lcaCache_ = std::make_unique<LCAEulerRMQ>(this);
609 }
610 return *lcaCache_;
611 }
612
616 template<class PreProcessing, class MergeProcessing, class PostProcessing>
617 static void computeIncrementalAttributes(MorphologicalTree* tree, NodeId rootNodeId, PreProcessing&& preProcessing, MergeProcessing&& mergeProcessing, PostProcessing&& postProcessing) {
618 preProcessing(rootNodeId);
620 computeIncrementalAttributes(tree, childNodeId, preProcessing, mergeProcessing, postProcessing);
621 mergeProcessing(rootNodeId, childNodeId);
622 }
623 postProcessing(rootNodeId);
624 }
625
626 // ========================= Internal topology helpers ========================= //
627 // These helpers mutate the dense node-indexed topology storage directly.
631 void linkChildSlot(NodeId parentSlotId, NodeId childId) {
632 if (parentSlotId < 0 || childId < 0) return;
633
634 // If the child already has a parent, detach it before reading sibling links.
635 if (nodeParent_[childId] != InvalidNode) {
636 unlinkChildSlot(nodeParent_[childId], childId, false);
637 }
638
639 nodeParent_[childId] = parentSlotId;
640 prevSibling_[childId] = lastChild_[parentSlotId];
641 nextSibling_[childId] = InvalidNode;
642
643 if (firstChild_[parentSlotId] == InvalidNode) {
644 firstChild_[parentSlotId] = lastChild_[parentSlotId] = childId;
645 } else {
646 nextSibling_[lastChild_[parentSlotId]] = childId;
647 lastChild_[parentSlotId] = childId;
648 }
649 ++numChildrenByNode_[parentSlotId];
650 bumpTopologyVersion();
651
652 }
653
657 inline void unlinkChildSlot(NodeId parentSlotId, NodeId childId, bool release) {
658 if (parentSlotId < 0 || childId < 0) return;
659 if (nodeParent_[childId] != parentSlotId) return;
660
661 const NodeId prev = prevSibling_[childId];
662 const NodeId next = nextSibling_[childId];
663
664 if (prev == InvalidNode)
665 firstChild_[parentSlotId] = next;
666 else
667 nextSibling_[prev] = next;
668
669 if (next == InvalidNode)
670 lastChild_[parentSlotId] = prev;
671 else
672 prevSibling_[next] = prev;
673
674 if (numChildrenByNode_[parentSlotId] > 0)
675 --numChildrenByNode_[parentSlotId];
676
677 nodeParent_[childId] = InvalidNode;
678 prevSibling_[childId] = InvalidNode;
679 nextSibling_[childId] = InvalidNode;
680 if(release){
681 releaseSlotNode(childId);
682 } else {
683 bumpTopologyVersion();
684 }
685 }
686
687
691 inline void spliceChildrenSlots(NodeId toId, NodeId fromId, ChildSplicePolicy policy = ChildSplicePolicy::AppendToTargetTail) {
692 if (toId < 0 || fromId < 0 || toId == fromId) return;
693
694 const NodeId firstFrom = firstChild_[fromId];
695 const NodeId lastFrom = lastChild_[fromId];
696 const int movedCount = numChildrenByNode_[fromId];
697 const bool replaceSourceSlot = policy == ChildSplicePolicy::ReplaceSourceSlotWhenDirectChild && nodeParent_[fromId] == toId;
698
699 for (NodeId childId = firstFrom; childId != InvalidNode; childId = nextSibling_[childId]) {
700 nodeParent_[childId] = toId;
701 }
702
703 if (replaceSourceSlot) {
704 const NodeId prev = prevSibling_[fromId];
705 const NodeId next = nextSibling_[fromId];
706
707 if (firstFrom == InvalidNode) {
708 if (prev == InvalidNode) {
709 firstChild_[toId] = next;
710 } else {
711 nextSibling_[prev] = next;
712 }
713
714 if (next == InvalidNode) {
715 lastChild_[toId] = prev;
716 } else {
717 prevSibling_[next] = prev;
718 }
719 } else {
720 if (prev == InvalidNode) {
721 firstChild_[toId] = firstFrom;
722 prevSibling_[firstFrom] = InvalidNode;
723 } else {
724 nextSibling_[prev] = firstFrom;
725 prevSibling_[firstFrom] = prev;
726 }
727
728 if (next == InvalidNode) {
729 lastChild_[toId] = lastFrom;
730 nextSibling_[lastFrom] = InvalidNode;
731 } else {
732 prevSibling_[next] = lastFrom;
733 nextSibling_[lastFrom] = next;
734 }
735 }
736
737 nodeParent_[fromId] = InvalidNode;
738 prevSibling_[fromId] = InvalidNode;
739 nextSibling_[fromId] = InvalidNode;
740 numChildrenByNode_[toId] += movedCount - 1;
741 } else {
742 if (firstFrom == InvalidNode) return;
743
744 if (firstChild_[toId] == InvalidNode) {
745 firstChild_[toId] = firstFrom;
746 lastChild_[toId] = lastFrom;
747 } else {
748 nextSibling_[lastChild_[toId]] = firstFrom;
749 prevSibling_[firstFrom] = lastChild_[toId];
750 lastChild_[toId] = lastFrom;
751 }
752
753 numChildrenByNode_[toId] += movedCount;
754 }
755
756 firstChild_[fromId] = InvalidNode;
757 lastChild_[fromId] = InvalidNode;
758 numChildrenByNode_[fromId] = 0;
759 bumpTopologyVersion();
760 }
761
762
763
767 inline void releaseNode(NodeId nodeId) noexcept {
768 if (!isNode(nodeId) || !isAlive(nodeId) || isRoot(nodeId)) {
769 return;
770 }
771 const NodeId nodeSlot = nodeId;
772 if (nodeParent_[nodeSlot] != nodeSlot) {
773 return;
774 }
775 if (numChildrenByNode_[nodeSlot] != 0 || numProperPartsByNode_[nodeSlot] != 0) {
776 return;
777 }
778 releaseSlotNode(nodeSlot);
779 }
780
784 inline NodeId allocateNode() {
785 if (freeNodeIds_.empty()) {
786 return InvalidNode;
787 }
788 const NodeId nodeSlot = allocateSlot();
789 nodeParent_[nodeSlot] = nodeSlot;
790 numNodes_++;
791 invalidatePrePostOrderCache();
792 bumpNodeStructureVersion();
793 return nodeSlot;
794 }
795
803 inline NodeId createDetachedNode() {
804 const NodeId nodeSlot = allocateSlot();
805 nodeParent_[static_cast<size_t>(nodeSlot)] = nodeSlot;
806 numNodes_++;
807 invalidatePrePostOrderCache();
808 bumpNodeStructureVersion();
809 return nodeSlot;
810 }
811
815 inline void removeChild(NodeId parentNodeId, NodeId childId, bool releaseNodeFlag) {
817 return;
818 }
820 const NodeId childSlotId = childId;
821 unlinkChildSlot(parentSlotId, childSlotId, false);
822 nodeParent_[childSlotId] = childSlotId;
823 invalidatePrePostOrderCache();
824 if (releaseNodeFlag) {
825 releaseNode(childId);
826 }
827 }
828
832 inline void attachNode(NodeId parentNodeId, NodeId nodeId) {
834 return;
835 }
837 const NodeId nodeSlotId = nodeId;
838 const NodeId oldParentSlotId = nodeParent_[nodeSlotId];
840 unlinkChildSlot(oldParentSlotId, nodeSlotId, false);
841 }
842 nodeParent_[nodeSlotId] = InvalidNode;
843 linkChildSlot(parentSlotId, nodeSlotId);
844 }
845
849 inline void detachNode(NodeId nodeId) {
850 if (!isAlive(nodeId) || isRoot(nodeId)) {
851 return;
852 }
853 const NodeId nodeSlotId = nodeId;
854 const NodeId parentSlotId = nodeParent_[nodeSlotId];
856 return;
857 }
858 unlinkChildSlot(parentSlotId, nodeSlotId, false);
859 nodeParent_[nodeSlotId] = nodeSlotId;
860 }
861
865 inline void moveNode(NodeId nodeId, NodeId newParentId) {
867 return;
868 }
869 const NodeId nodeSlotId = nodeId;
871 const NodeId oldParentSlotId = nodeParent_[nodeSlotId];
873 return;
874 }
876 unlinkChildSlot(oldParentSlotId, nodeSlotId, false);
877 }
878 nodeParent_[nodeSlotId] = InvalidNode;
879 linkChildSlot(newParentSlotId, nodeSlotId);
880 }
881
885 inline void moveChildren(NodeId parentNodeId, NodeId sourceId) {
887 return;
888 }
889 spliceChildrenSlots(parentNodeId, sourceId);
890 }
891
895 inline void moveProperPart(NodeId targetNodeId, NodeId sourceNodeId, NodeId pixelId) {
897 return;
898 }
900 if (properPartOwner_[pixelId] != sourceSlotId) {
901 return;
902 }
903 unlinkProperPartFromOwner(sourceSlotId, pixelId);
904 appendDetachedProperPart(targetNodeId, pixelId);
905 bumpProperPartVersion();
906 }
907
911 inline void moveProperParts(NodeId targetNodeId, NodeId sourceNodeId) {
913 return;
914 }
915 if (properHead_[static_cast<size_t>(sourceNodeId)] == InvalidNode) {
916 return;
917 }
918 spliceProperPartsSlots(targetNodeId, sourceNodeId);
919 bumpProperPartVersion();
920 }
921
925 inline void setRoot(NodeId nodeId) {
926 if (!isAlive(nodeId)) {
927 return;
928 }
929 const NodeId nodeSlot = nodeId;
930 if (rootNodeId_ == nodeSlot) {
931 return;
932 }
933 if (rootNodeId_ != InvalidNode && !isFreeSlot(rootNodeId_)) {
934 nodeParent_[rootNodeId_] = rootNodeId_;
935 prevSibling_[rootNodeId_] = InvalidNode;
936 nextSibling_[rootNodeId_] = InvalidNode;
937 }
938 const NodeId oldParentSlot = nodeParent_[nodeSlot];
940 unlinkChildSlot(oldParentSlot, nodeSlot, false);
941 }
942 rootNodeId_ = nodeSlot;
943 nodeParent_[nodeSlot] = nodeSlot;
944 prevSibling_[nodeSlot] = InvalidNode;
945 nextSibling_[nodeSlot] = InvalidNode;
946 bumpTopologyVersion();
947 }
948
952 MorphologicalTree() = default;
953
954
955public:
960
965
970
975
980
990 requireNonEmptyImageDomain(imgPtr, "MorphologicalTree component-tree construction");
991
992 const bool isMaxtree = kind == ComponentTreeKind::MAX_TREE;
993 treeType_ = isMaxtree ? MorphologicalTreeKind::MAX_TREE : MorphologicalTreeKind::MIN_TREE;
994 numRows_ = imgPtr->getNumRows();
995 numCols_ = imgPtr->getNumCols();
996 adj_.emplace(numRows_, numCols_, radius);
997 tosAdjacencyPolicy_ = std::nullopt;
998 initializeEmptyStorage(static_cast<size_t>(imgPtr->getSize()));
999
1001 auto [parent, orderedPixels, numBuiltNodes] = builderUF.createTreeByUnionFind<PixelType>(imgPtr);
1002
1003 const int numPixels = imgPtr->getSize();
1004 auto img = imgPtr->rawData();
1005 this->reserveNodes(numBuiltNodes);
1006 for (int i = 0; i < numPixels; i++) {
1007 const int p = orderedPixels[i];
1008
1009 if (p == parent[p]) {
1010 properPartOwner_[p] = this->rootNodeId_ = this->makeNode(InvalidNode);
1011 } else if (img[p] != img[parent[p]]) {
1012 properPartOwner_[p] = this->makeNode(properPartOwner_[parent[p]]);
1013 } else {
1014 properPartOwner_[p] = properPartOwner_[parent[p]];
1015 }
1016 }
1017
1018 properHead_.assign(nodeParent_.size(), InvalidNode);
1019 properTail_.assign(nodeParent_.size(), InvalidNode);
1020 numProperPartsByNode_.assign(nodeParent_.size(), 0);
1021 initializeProperPartStorage(numPixels);
1022 rebuildProperPartLinksFromOwnership();
1023 invalidateAllIterators();
1024
1025 }
1026
1035 requireNonEmptyImageDomain(imgPtr, "MorphologicalTree tree-of-shapes construction");
1036
1037 treeType_ = MorphologicalTreeKind::TREE_OF_SHAPES;
1038 numRows_ = imgPtr->getNumRows();
1039 numCols_ = imgPtr->getNumCols();
1040 adj_ = std::nullopt;
1041 tosAdjacencyPolicy_ = treeOfShapesAdjacencyPolicy(interpolation, numRows_, numCols_);
1042 initializeEmptyStorage(static_cast<size_t>(imgPtr->getSize()));
1043
1045 auto [parent, orderedPixels, numBuiltNodes] = builderUF.createTreeByUnionFind(imgPtr);
1046
1047 const int numPixels = imgPtr->getSize();
1048 auto img = imgPtr->rawData();
1049
1050 this->reserveNodes(numBuiltNodes);
1051 for (int i = 0; i < numPixels; i++) {
1052 const int p = orderedPixels[i];
1053
1054 if (p == parent[p]) {
1055 properPartOwner_[p] = this->rootNodeId_ = this->makeNode(InvalidNode);
1056 } else if (img[p] != img[parent[p]]) {
1057 properPartOwner_[p] = this->makeNode(properPartOwner_[parent[p]]);
1058 } else {
1059 properPartOwner_[p] = properPartOwner_[parent[p]];
1060 }
1061 }
1062
1063 properHead_.assign(nodeParent_.size(), InvalidNode);
1064 properTail_.assign(nodeParent_.size(), InvalidNode);
1065 numProperPartsByNode_.assign(nodeParent_.size(), 0);
1066 initializeProperPartStorage(numPixels);
1067 rebuildProperPartLinksFromOwnership();
1068 invalidateAllIterators();
1069 }
1070
1080 MorphologicalTree(detail::MorphologicalTreeConstructionTag, std::span<const NodeId> parent, int rows, int cols, MorphologicalTreeKind kind, std::optional<AdjacencyRelation> adjacency) {
1081 if (kind == MorphologicalTreeKind::SELF_DUAL_RESIDUAL_TREE) {
1082 throw std::invalid_argument("Higra import does not accept SELF_DUAL_RESIDUAL_TREE.");
1083 }
1084 if (!adjacency && kind != MorphologicalTreeKind::TREE_OF_SHAPES) {
1085 throw std::invalid_argument("Higra import of max/min trees requires an explicit adjacency relation.");
1086 }
1087
1088 treeType_ = kind;
1089 numRows_ = rows;
1090 numCols_ = cols;
1091 adj_ = std::move(adjacency);
1092 tosAdjacencyPolicy_ = std::nullopt;
1093
1094 const NodeId numProperParts = static_cast<NodeId>(rows * cols);
1095 if (adj_) {
1096 if (adj_->getNumRows() * adj_->getNumCols() != numProperParts) {
1097 throw std::invalid_argument("Higra leaf count must match image domain size.");
1098 }
1099 }
1100
1101 const NodeId numHigraNodes = static_cast<NodeId>(parent.size());
1103 throw std::invalid_argument("Higra parent array must contain leaves followed by at least one internal node.");
1104 }
1106
1107 initializeEmptyStorage(static_cast<size_t>(numProperParts));
1108 nodeParent_.assign(static_cast<size_t>(numNodeSlots), InvalidNode);
1109 firstChild_.assign(static_cast<size_t>(numNodeSlots), InvalidNode);
1110 nextSibling_.assign(static_cast<size_t>(numNodeSlots), InvalidNode);
1111 prevSibling_.assign(static_cast<size_t>(numNodeSlots), InvalidNode);
1112 lastChild_.assign(static_cast<size_t>(numNodeSlots), InvalidNode);
1113 numChildrenByNode_.assign(static_cast<size_t>(numNodeSlots), 0);
1114 alive_.assign(static_cast<size_t>(numNodeSlots), 1);
1115 properHead_.assign(static_cast<size_t>(numNodeSlots), InvalidNode);
1116 properTail_.assign(static_cast<size_t>(numNodeSlots), InvalidNode);
1117 numProperPartsByNode_.assign(static_cast<size_t>(numNodeSlots), 0);
1118 initializeProperPartStorage(static_cast<size_t>(numProperParts));
1119
1120 numNodes_ = static_cast<int>(numNodeSlots);
1121 rootNodeId_ = InvalidNode;
1122
1124 const NodeId ownerHigraNodeId = parent[static_cast<size_t>(properPartId)];
1126 throw std::invalid_argument("Each Higra leaf must point to an internal node.");
1127 }
1128 properPartOwner_[static_cast<size_t>(properPartId)] = ownerHigraNodeId - numProperParts;
1129 }
1130
1131 for (NodeId slotId = 0; slotId < numNodeSlots; ++slotId) {
1133 const NodeId parentHigraNodeId = parent[static_cast<size_t>(higraNodeId)];
1135 if (rootNodeId_ != InvalidNode) {
1136 throw std::invalid_argument("A Higra hierarchy must encode exactly one root.");
1137 }
1138 nodeParent_[static_cast<size_t>(slotId)] = slotId;
1139 rootNodeId_ = slotId;
1140 continue;
1141 }
1142
1144 throw std::invalid_argument("Each Higra internal node must point to another internal node or to itself.");
1145 }
1147 linkChildSlot(parentSlotId, slotId);
1148 }
1149
1150 if (rootNodeId_ == InvalidNode) {
1151 throw std::invalid_argument("A Higra hierarchy must encode exactly one root.");
1152 }
1153 rebuildProperPartLinksFromOwnership();
1154 invalidatePrePostOrderCache();
1155 invalidateAllIterators();
1156 preservesHigraNodeIdSpace_ = true;
1157 }
1158
1167 MorphologicalTree(detail::MorphologicalTreeConstructionTag, std::span<const NodeId> nodeParent, std::span<const NodeId> properPartOwner, NodeId root, int rows, int cols) {
1168 if (properPartOwner.size() != static_cast<size_t>(rows * cols)) {
1169 throw std::invalid_argument("Self-dual residual tree proper-part domain must match rows * cols.");
1170 }
1171
1172 const NodeId numNodeSlots = static_cast<NodeId>(nodeParent.size());
1173 const NodeId numProperParts = static_cast<NodeId>(properPartOwner.size());
1174 if (numNodeSlots <= 0) {
1175 throw std::invalid_argument("Native topology import requires at least one internal node.");
1176 }
1177 if (numProperParts <= 0) {
1178 throw std::invalid_argument("Native topology import requires at least one proper part.");
1179 }
1181 throw std::invalid_argument("Native topology import requires a valid root node id.");
1182 }
1183
1184 treeType_ = MorphologicalTreeKind::SELF_DUAL_RESIDUAL_TREE;
1185 numRows_ = rows;
1186 numCols_ = cols;
1187 adj_ = std::nullopt;
1188 tosAdjacencyPolicy_ = std::nullopt;
1189
1190 initializeEmptyStorage(static_cast<size_t>(numProperParts));
1191 nodeParent_.assign(nodeParent.begin(), nodeParent.end());
1192 firstChild_.assign(static_cast<size_t>(numNodeSlots), InvalidNode);
1193 nextSibling_.assign(static_cast<size_t>(numNodeSlots), InvalidNode);
1194 prevSibling_.assign(static_cast<size_t>(numNodeSlots), InvalidNode);
1195 lastChild_.assign(static_cast<size_t>(numNodeSlots), InvalidNode);
1196 numChildrenByNode_.assign(static_cast<size_t>(numNodeSlots), 0);
1197 alive_.assign(static_cast<size_t>(numNodeSlots), 1);
1198 freeNodeIds_.clear();
1199 properHead_.assign(static_cast<size_t>(numNodeSlots), InvalidNode);
1200 properTail_.assign(static_cast<size_t>(numNodeSlots), InvalidNode);
1201 numProperPartsByNode_.assign(static_cast<size_t>(numNodeSlots), 0);
1202 properPartOwner_.assign(properPartOwner.begin(), properPartOwner.end());
1203 initializeProperPartStorage(static_cast<size_t>(numProperParts));
1204
1205 rootNodeId_ = root;
1206 numNodes_ = static_cast<int>(numNodeSlots);
1207
1208 int selfParentedRoots = 0;
1209 for (NodeId nodeId = 0; nodeId < numNodeSlots; ++nodeId) {
1210 const NodeId parentId = nodeParent_[static_cast<size_t>(nodeId)];
1211 if (parentId == nodeId) {
1212 if (nodeId != root) {
1213 throw std::invalid_argument("Native topology import found a detached self-parented non-root node.");
1214 }
1216 continue;
1217 }
1219 throw std::invalid_argument("Native topology import found a parent outside the internal-node domain.");
1220 }
1221
1222 prevSibling_[static_cast<size_t>(nodeId)] = lastChild_[static_cast<size_t>(parentId)];
1223 if (lastChild_[static_cast<size_t>(parentId)] == InvalidNode) {
1224 firstChild_[static_cast<size_t>(parentId)] = nodeId;
1225 } else {
1226 nextSibling_[static_cast<size_t>(lastChild_[static_cast<size_t>(parentId)])] = nodeId;
1227 }
1228 lastChild_[static_cast<size_t>(parentId)] = nodeId;
1229 ++numChildrenByNode_[static_cast<size_t>(parentId)];
1230 }
1231 if (selfParentedRoots != 1) {
1232 throw std::invalid_argument("Native topology import must encode exactly one self-parented root.");
1233 }
1234
1236 const NodeId ownerId = properPartOwner_[static_cast<size_t>(properPartId)];
1238 throw std::invalid_argument("Native topology import found a proper-part owner outside the internal-node domain.");
1239 }
1240 }
1241
1242 rebuildProperPartLinksFromOwnership();
1243 invalidatePrePostOrderCache();
1244 invalidateAllIterators();
1245 preservesHigraNodeIdSpace_ = false;
1246 }
1247
1257 cloned.rootNodeId_ = rootNodeId_;
1258 cloned.treeType_ = treeType_;
1259 cloned.numRows_ = numRows_;
1260 cloned.numCols_ = numCols_;
1261 cloned.adj_ = adj_;
1262 cloned.tosAdjacencyPolicy_ = tosAdjacencyPolicy_;
1263 cloned.numNodes_ = numNodes_;
1264 cloned.preservesHigraNodeIdSpace_ = preservesHigraNodeIdSpace_;
1265 cloned.editSessionOpen_ = false;
1266 cloned.properPartOwner_ = properPartOwner_;
1267 cloned.nodeParent_ = nodeParent_;
1268 cloned.firstChild_ = firstChild_;
1269 cloned.nextSibling_ = nextSibling_;
1270 cloned.prevSibling_ = prevSibling_;
1271 cloned.lastChild_ = lastChild_;
1272 cloned.numChildrenByNode_ = numChildrenByNode_;
1273 cloned.alive_ = alive_;
1274 cloned.freeNodeIds_ = freeNodeIds_;
1275 cloned.properHead_ = properHead_;
1276 cloned.properTail_ = properTail_;
1277 cloned.numProperPartsByNode_ = numProperPartsByNode_;
1278 cloned.nextProperPart_ = nextProperPart_;
1279 cloned.prevProperPart_ = prevProperPart_;
1280 cloned.prePostOrderCache_ = prePostOrderCache_;
1281 cloned.lcaCache_.reset();
1282 cloned.nodeStructureVersion_ = nodeStructureVersion_;
1283 cloned.topologyVersion_ = topologyVersion_;
1284 cloned.properPartVersion_ = properPartVersion_;
1285 cloned.mutationVersion_ = mutationVersion_;
1286 return cloned;
1287 }
1288
1289 // Forward declarations for nested iterator/range types whose definitions stay at the end of the class.
1290 class AliveNodeIterator;
1291 class AliveNodeRange;
1292 class ChildrenIterator;
1293 class ChildrenRange;
1294 class ProperPartsIterator;
1295 class ProperPartsRange;
1296 class ConnectedComponentIterator;
1297 class ConnectedComponentRange;
1298 class PostOrderNodeIterator;
1299 class PostOrderNodeRange;
1300 class BreadthFirstNodeIterator;
1301 class BreadthFirstNodeRange;
1302 class PathToRootIterator;
1303 class PathToRootRange;
1304 class PathBetweenNodesIterator;
1305 class PathBetweenNodesRange;
1306 class SubtreeNodeIterator;
1307 class SubtreeNodeRange;
1308 class DescendantNodeRange;
1309
1310public:
1311 // ========================= Public methods ========================= //
1312
1316 std::size_t getMutationVersion() const noexcept { return mutationVersion_;}
1317
1321 void requireMutationVersion(std::size_t expectedVersion, const char* context) const {
1322 if (mutationVersion_ != expectedVersion) {
1323 throw std::logic_error(std::string(context) + " cannot be used after the referenced tree topology has changed.");
1324 }
1325 }
1326
1332 inline int getNumInternalNodeSlots() const { return static_cast<int>(nodeParent_.size()); }
1333
1337 inline int getNumTotalProperParts() const { return static_cast<int>(properPartOwner_.size()); }
1338
1345 inline int getNumHigraNodes() const {
1346 if (!preservesHigraNodeIdSpace_) {
1347 throw std::runtime_error("This tree does not preserve an imported Higra node-id space.");
1348 }
1350 }
1351
1358 inline int getNodeIdSpaceSize(NodeIdSpace outputSpace) const {
1359 switch (outputSpace) {
1360 case NodeIdSpace::MORPHOLOGICAL_TREE:
1361 return getNumInternalNodeSlots();
1362 case NodeIdSpace::HIGRA:
1363 return getNumHigraNodes();
1364 }
1365 throw std::runtime_error("Unknown NodeIdSpace.");
1366 }
1367
1374 inline NodeId getHigraNodeId(NodeId nodeId) const noexcept {
1375 if (!preservesHigraNodeIdSpace_ || !isNode(nodeId) || !isAlive(nodeId)) {
1376 return InvalidNode;
1377 }
1378 return getNumTotalProperParts() + nodeId;
1379 }
1380
1384 inline NodeId getRoot() const { return rootNodeId_; }
1385
1389 inline bool isNode(NodeId nodeId) const noexcept { return nodeId >= 0 && nodeId < static_cast<int>(nodeParent_.size()); }
1390
1394 inline bool isProperPart(NodeId id) const noexcept { return id >= 0 && id < static_cast<int>(properPartOwner_.size()); }
1395
1399 inline bool isAlive(NodeId nodeId) const {
1400 if (!isNode(nodeId)) {
1401 return false;
1402 }
1403 const NodeId localId = nodeId;
1404 return localId >= 0
1405 && localId < static_cast<NodeId>(nodeParent_.size())
1406 && !isFreeSlot(localId)
1407 && (nodeParent_[localId] != InvalidNode || localId == rootNodeId_);
1408 }
1409
1413 inline bool isRoot(NodeId nodeId) const { return nodeId == getRoot(); }
1414
1418 inline int getNumFreeNodeSlots() const { return static_cast<int>(freeNodeIds_.size()); }
1419
1423 inline int getNumLeafNodes() const {
1424 int count = 0;
1425 for (NodeId nodeId : getAliveNodeIds()) {
1426 if (isLeaf(nodeId)) {
1427 ++count;
1428 }
1429 }
1430 return count;
1431 }
1432
1436 inline int getNumChildren(NodeId nodeId) const {
1437 requireAliveNode(nodeId, "MorphologicalTree::getNumChildren");
1438 return numChildrenByNode_[nodeId];
1439 }
1440
1445 requireAliveNode(nodeId, "MorphologicalTree::getNodeNumDescendants");
1446 ensurePrePostOrderCache();
1447 const NodeId localId = nodeId;
1448 return (prePostOrderCache_.timePostOrder[localId] - prePostOrderCache_.timePreOrder[localId] - 1) / 2;
1449 }
1450
1455 requireAliveNode(nodeId, "MorphologicalTree::getNodeNumSiblings");
1456 if (isRoot(nodeId)) {
1457 return 0;
1458 }
1460 return std::max(0, getNumChildren(parentNodeId) - 1);
1461 }
1462
1467 requireAliveNode(nodeId, "MorphologicalTree::getNodeTimePreOrder");
1468 ensurePrePostOrderCache();
1469 return prePostOrderCache_.timePreOrder[nodeId];
1470 }
1471
1476 requireAliveNode(nodeId, "MorphologicalTree::getNodeTimePostOrder");
1477 ensurePrePostOrderCache();
1478 return prePostOrderCache_.timePostOrder[nodeId];
1479 }
1480
1485 requireAliveNode(nodeId, "MorphologicalTree::getFirstChild");
1486 return firstChild_[nodeId];
1487 }
1488
1493 requireAliveNode(nodeId, "MorphologicalTree::getNextSibling");
1494 return nextSibling_[nodeId];
1495 }
1496
1500 inline bool isLeaf(NodeId nodeId) const { return getFirstChild(nodeId) == InvalidNode; }
1501
1505 inline int getNumProperParts(NodeId nodeId) const {
1506 requireAliveNode(nodeId, "MorphologicalTree::getNumProperParts");
1507 return numProperPartsByNode_[nodeId];
1508 }
1509
1514 requireAliveNode(parentNodeId, "MorphologicalTree::hasChild");
1515 requireAliveNode(childId, "MorphologicalTree::hasChild");
1517 }
1518
1525 requireAliveNode(nodeId, "MorphologicalTree::getNodeParent");
1526 if (isRoot(nodeId)) {
1527 return nodeId;
1528 }
1529 const NodeId parentSlot = nodeParent_[nodeId];
1530 if (parentSlot == InvalidNode) {
1531 return InvalidNode;
1532 }
1533 if (parentSlot == nodeId) {
1534 return nodeId;
1535 }
1536 return parentSlot;
1537 }
1538
1544 ? properPartOwner_[static_cast<size_t>(properPartId)]
1545 : InvalidNode;
1546 }
1547
1551 std::vector<NodeId> getLeaves() const {
1552 std::vector<NodeId> leaves;
1553 if (rootNodeId_ == InvalidNode) {
1554 return leaves;
1555 }
1557 s.push(this->rootNodeId_);
1558
1559 while (!s.empty()) {
1560 const NodeId id = s.pop();
1561 if (numChildrenByNode_[id] == 0) {
1562 leaves.push_back(id);
1563 } else {
1564 for (NodeId c = firstChild_[id]; c != InvalidNode; c = nextSibling_[c]) {
1565 s.push(c);
1566 }
1567 }
1568 }
1569 return leaves;
1570 }
1571
1575 inline MorphologicalTreeKind getTreeType() const noexcept{return treeType_;}
1576
1580 inline int getNumNodes()const noexcept{ return numNodes_; }
1581
1585 inline bool isEditing() const noexcept { return editSessionOpen_; }
1586
1590 inline void requireNotEditing(const char* context) const {
1591 if (isEditing()) {
1592 throw std::logic_error(std::string(context) + " requires a committed MorphologicalTree; an edit session is still open.");
1593 }
1594 }
1595
1604 if (numNodes_ == 0) {
1605 return false;
1606 }
1607 if (rootNodeId_ == InvalidNode || !isAlive(rootNodeId_)) {
1608 return true;
1609 }
1610
1611 for (NodeId nodeId = 0; nodeId < static_cast<NodeId>(nodeParent_.size()); ++nodeId) {
1612 if (!isAlive(nodeId) || nodeId == rootNodeId_) {
1613 continue;
1614 }
1615 if (nodeParent_[nodeId] == nodeId) {
1616 return true;
1617 }
1618 }
1619 return false;
1620 }
1621
1625 inline bool hasAdjacencyRelation() const noexcept { return static_cast<bool>(adj_); }
1626
1630 inline bool hasTreeOfShapesAdjacencyPolicy() const noexcept { return static_cast<bool>(tosAdjacencyPolicy_); }
1631
1636 if (!tosAdjacencyPolicy_) {
1637 throw std::runtime_error("Tree-of-shapes adjacency policy is not available.");
1638 }
1639 return tosAdjacencyPolicy_->first.getRadius();
1640 }
1641
1646 if (!tosAdjacencyPolicy_) {
1647 throw std::runtime_error("Tree-of-shapes adjacency policy is not available.");
1648 }
1649 return tosAdjacencyPolicy_->second.getRadius();
1650 }
1651
1656 return tosAdjacencyPolicy_ ? &tosAdjacencyPolicy_->first : nullptr;
1657 }
1658
1663 return tosAdjacencyPolicy_ ? &tosAdjacencyPolicy_->second : nullptr;
1664 }
1665
1669 inline int getNumRowsOfImage() const {
1670 return numRows_;
1671 }
1672
1676 inline int getNumColsOfImage() const {
1677 return numCols_;
1678 }
1679
1683 inline AdjacencyRelation* getAdjacencyRelation() noexcept {return adj_ ? &*adj_ : nullptr;}
1684
1688 inline const AdjacencyRelation* getAdjacencyRelation() const noexcept {return adj_ ? &*adj_ : nullptr;}
1689
1698 if (numNodes_ <= 0) {
1699 throw std::runtime_error("Connected-tree validation requires at least one live node.");
1700 }
1701 if (!isAlive(rootNodeId_)) {
1702 throw std::runtime_error("Connected-tree validation requires a live root.");
1703 }
1704 if (nodeParent_[rootNodeId_] != rootNodeId_) {
1705 throw std::runtime_error("Connected-tree validation requires the root to point to itself.");
1706 }
1707
1708 const int numSlots = getNumInternalNodeSlots();
1710 std::vector<int> expectedChildrenByNode(static_cast<size_t>(numSlots), 0);
1711 std::vector<int> expectedProperPartsByNode(static_cast<size_t>(numSlots), 0);
1712 std::vector<int> parentChainStamp(static_cast<size_t>(numSlots), 0);
1713 int aliveCount = 0;
1714 int stamp = 0;
1715
1716 for (NodeId nodeId = 0; nodeId < numSlots; ++nodeId) {
1717 if (!isAlive(nodeId)) {
1718 continue;
1719 }
1720 ++aliveCount;
1721 ++stamp;
1722
1724 while (true) {
1726 throw std::runtime_error("Connected-tree validation found a parent chain that leaves the alive node domain.");
1727 }
1728 if (parentChainStamp[static_cast<size_t>(currentNodeId)] == stamp) {
1729 throw std::runtime_error("Connected-tree validation found a cycle in the parent chain.");
1730 }
1731 parentChainStamp[static_cast<size_t>(currentNodeId)] = stamp;
1732
1733 const NodeId parentNodeId = nodeParent_[static_cast<size_t>(currentNodeId)];
1734 if (parentNodeId == InvalidNode) {
1735 throw std::runtime_error("Connected-tree validation found an alive node with no parent.");
1736 }
1737 if (parentNodeId == currentNodeId) {
1738 if (currentNodeId != rootNodeId_) {
1739 throw std::runtime_error("Connected-tree validation found a detached alive node.");
1740 }
1741 break;
1742 }
1744 throw std::runtime_error("Connected-tree validation found an alive node whose parent is outside the alive node domain.");
1745 }
1747 }
1748
1749 if (nodeId != rootNodeId_) {
1750 expectedChildrenByNode[static_cast<size_t>(nodeParent_[static_cast<size_t>(nodeId)])] += 1;
1751 }
1752 }
1753
1754 if (aliveCount != numNodes_) {
1755 throw std::runtime_error("Connected-tree validation found getNumNodes() out of sync with the alive node slots.");
1756 }
1757
1758 std::vector<uint8_t> seenAsChild(static_cast<size_t>(numSlots), 0);
1759 for (NodeId nodeId = 0; nodeId < numSlots; ++nodeId) {
1760 if (!isAlive(nodeId)) {
1761 continue;
1762 }
1763
1764 int actualChildCount = 0;
1766 for (NodeId childNodeId = firstChild_[static_cast<size_t>(nodeId)];
1768 childNodeId = nextSibling_[static_cast<size_t>(childNodeId)]) {
1770 throw std::runtime_error("Connected-tree validation found a child list that references a non-alive node.");
1771 }
1772 if (nodeParent_[static_cast<size_t>(childNodeId)] != nodeId) {
1773 throw std::runtime_error("Connected-tree validation found a child whose parent pointer disagrees with the child list.");
1774 }
1775 if (prevSibling_[static_cast<size_t>(childNodeId)] != previousChildId) {
1776 throw std::runtime_error("Connected-tree validation found broken previous-sibling links.");
1777 }
1778 if (seenAsChild[static_cast<size_t>(childNodeId)] != 0) {
1779 throw std::runtime_error("Connected-tree validation found a node referenced by multiple child lists.");
1780 }
1781 seenAsChild[static_cast<size_t>(childNodeId)] = 1;
1785 throw std::runtime_error("Connected-tree validation found a cycle in a child list.");
1786 }
1787 }
1788
1789 if (firstChild_[static_cast<size_t>(nodeId)] == InvalidNode) {
1790 if (lastChild_[static_cast<size_t>(nodeId)] != InvalidNode) {
1791 throw std::runtime_error("Connected-tree validation found an empty child list with a non-empty tail pointer.");
1792 }
1793 } else {
1794 if (lastChild_[static_cast<size_t>(nodeId)] != previousChildId) {
1795 throw std::runtime_error("Connected-tree validation found an incorrect last-child pointer.");
1796 }
1797 if (nextSibling_[static_cast<size_t>(previousChildId)] != InvalidNode) {
1798 throw std::runtime_error("Connected-tree validation found a child list whose tail still points to a next sibling.");
1799 }
1800 }
1801
1802 if (actualChildCount != numChildrenByNode_[static_cast<size_t>(nodeId)]) {
1803 throw std::runtime_error("Connected-tree validation found an incorrect child count cache.");
1804 }
1805 if (actualChildCount != expectedChildrenByNode[static_cast<size_t>(nodeId)]) {
1806 throw std::runtime_error("Connected-tree validation found child lists out of sync with parent pointers.");
1807 }
1808 }
1809
1810 if (seenAsChild[static_cast<size_t>(rootNodeId_)] != 0) {
1811 throw std::runtime_error("Connected-tree validation found the root inside a child list.");
1812 }
1813 for (NodeId nodeId = 0; nodeId < numSlots; ++nodeId) {
1814 if (!isAlive(nodeId) || nodeId == rootNodeId_) {
1815 continue;
1816 }
1817 if (seenAsChild[static_cast<size_t>(nodeId)] == 0) {
1818 throw std::runtime_error("Connected-tree validation found a non-root alive node missing from the child lists.");
1819 }
1820 }
1821
1823 const NodeId ownerNodeId = properPartOwner_[static_cast<size_t>(properPartId)];
1824 if (!isAlive(ownerNodeId)) {
1825 throw std::runtime_error("Connected-tree validation found a proper part without an alive owner.");
1826 }
1827 expectedProperPartsByNode[static_cast<size_t>(ownerNodeId)] += 1;
1828 }
1829
1830 std::vector<uint8_t> seenProperPart(static_cast<size_t>(numProperParts), 0);
1831 for (NodeId nodeId = 0; nodeId < numSlots; ++nodeId) {
1832 if (!isAlive(nodeId)) {
1833 continue;
1834 }
1835
1836 int actualProperPartCount = 0;
1838 for (NodeId properPartId = properHead_[static_cast<size_t>(nodeId)];
1840 properPartId = nextProperPart_[static_cast<size_t>(properPartId)]) {
1841 if (!isProperPart(properPartId)) {
1842 throw std::runtime_error("Connected-tree validation found an invalid proper-part id in the ownership list.");
1843 }
1844 if (properPartOwner_[static_cast<size_t>(properPartId)] != nodeId) {
1845 throw std::runtime_error("Connected-tree validation found a proper-part list that disagrees with the ownership map.");
1846 }
1847 if (prevProperPart_[static_cast<size_t>(properPartId)] != previousProperPartId) {
1848 throw std::runtime_error("Connected-tree validation found broken previous-proper-part links.");
1849 }
1850 if (seenProperPart[static_cast<size_t>(properPartId)] != 0) {
1851 throw std::runtime_error("Connected-tree validation found a proper part referenced multiple times.");
1852 }
1853 seenProperPart[static_cast<size_t>(properPartId)] = 1;
1857 throw std::runtime_error("Connected-tree validation found a cycle in a proper-part list.");
1858 }
1859 }
1860
1861 if (properHead_[static_cast<size_t>(nodeId)] == InvalidNode) {
1862 if (properTail_[static_cast<size_t>(nodeId)] != InvalidNode) {
1863 throw std::runtime_error("Connected-tree validation found an empty proper-part list with a non-empty tail pointer.");
1864 }
1865 } else {
1866 if (properTail_[static_cast<size_t>(nodeId)] != previousProperPartId) {
1867 throw std::runtime_error("Connected-tree validation found an incorrect proper-part tail pointer.");
1868 }
1869 if (nextProperPart_[static_cast<size_t>(previousProperPartId)] != InvalidNode) {
1870 throw std::runtime_error("Connected-tree validation found a proper-part list whose tail still points forward.");
1871 }
1872 }
1873
1874 if (actualProperPartCount != numProperPartsByNode_[static_cast<size_t>(nodeId)]) {
1875 throw std::runtime_error("Connected-tree validation found an incorrect direct proper-part count cache.");
1876 }
1877 if (actualProperPartCount != expectedProperPartsByNode[static_cast<size_t>(nodeId)]) {
1878 throw std::runtime_error("Connected-tree validation found proper-part lists out of sync with the ownership map.");
1879 }
1880 }
1881
1883 if (seenProperPart[static_cast<size_t>(properPartId)] == 0) {
1884 throw std::runtime_error("Connected-tree validation found a proper part missing from the ownership lists.");
1885 }
1886 }
1887 }
1888
1893 try {
1895 return {true, ""};
1896 } catch (const std::exception& ex) {
1897 return {false, ex.what()};
1898 } catch (...) {
1899 return {false, "Connected-tree validation failed with an unknown error."};
1900 }
1901 }
1902
1906 inline bool isAncestor(NodeId u, NodeId v) const {
1907 requireAliveNode(u, "MorphologicalTree::isAncestor");
1908 requireAliveNode(v, "MorphologicalTree::isAncestor");
1909 ensurePrePostOrderCache();
1910 const NodeId slotU = u;
1911 const NodeId slotV = v;
1912 return prePostOrderCache_.timePreOrder[slotU] <= prePostOrderCache_.timePreOrder[slotV]
1913 && prePostOrderCache_.timePostOrder[slotU] >= prePostOrderCache_.timePostOrder[slotV];
1914 }
1915
1919 inline bool isDescendant(NodeId u, NodeId v) const {
1920 requireAliveNode(u, "MorphologicalTree::isDescendant");
1921 requireAliveNode(v, "MorphologicalTree::isDescendant");
1922 ensurePrePostOrderCache();
1923 const NodeId slotU = u;
1924 const NodeId slotV = v;
1925 return prePostOrderCache_.timePreOrder[slotV] <= prePostOrderCache_.timePreOrder[slotU]
1926 && prePostOrderCache_.timePostOrder[slotV] >= prePostOrderCache_.timePostOrder[slotU];
1927 }
1931 inline bool isComparable(NodeId u, NodeId v) const {return isAncestor(u, v) || isAncestor(v, u);}
1932
1936 inline bool isStrictAncestor(NodeId u, NodeId v) const {return u != v && isAncestor(u, v);}
1937
1941 inline bool isStrictDescendant(NodeId u, NodeId v) const { return u != v && isDescendant(u, v);}
1942
1946 inline bool isStrictComparable(NodeId u, NodeId v) const { return isStrictAncestor(u, v) || isStrictAncestor(v, u);}
1947
1952 if (!isAlive(u) || !isAlive(v)) {
1953 return InvalidNode;
1954 }
1955 if (u == v) {
1956 return u;
1957 }
1958 if (isAncestor(u, v)) {
1959 return u;
1960 }
1961 if (isAncestor(v, u)) {
1962 return v;
1963 }
1964 if (getNodeParent(u) == u || getNodeParent(v) == v) {
1965 return InvalidNode;
1966 }
1967 return ensureLcaCache().findLowestCommonAncestor(u, v);
1968 }
1969
1974 return AliveNodeRange(this, 0, getNumInternalNodeSlots(), nodeStructureVersion_);
1975 }
1976
1981 requireAliveNode(nodeId, "MorphologicalTree::getChildren");
1982 return ChildrenRange(this, firstChild_[nodeId], topologyVersion_);
1983 }
1984
1989 requireAliveNode(nodeId, "MorphologicalTree::getProperParts");
1990 return ProperPartsRange(this, properHead_[nodeId], properPartVersion_);
1991 }
1992
2001 requireAliveNode(nodeId, "MorphologicalTree::getConnectedComponent");
2002 return ConnectedComponentRange(this, nodeId, topologyVersion_, properPartVersion_);
2003 }
2004
2009 return PostOrderNodeRange(this, getRoot(), topologyVersion_);
2010 }
2011
2016 requireAliveNode(rootNodeId, "MorphologicalTree::getPostOrderNodes");
2017 return PostOrderNodeRange(this, rootNodeId, topologyVersion_);
2018 }
2019
2024 return BreadthFirstNodeRange(this, getRoot(), topologyVersion_);
2025 }
2026
2031 requireAliveNode(rootNodeId, "MorphologicalTree::getIteratorBreadthFirstTraversal");
2032 return BreadthFirstNodeRange(this, rootNodeId, topologyVersion_);
2033 }
2034
2039 requireAliveNode(nodeId, "MorphologicalTree::getPathToRootNodes");
2040 return PathToRootRange(this, nodeId, topologyVersion_);
2041 }
2042
2047 requireAliveNode(sourceNodeId, "MorphologicalTree::getPathBetweenNodes");
2048 requireAliveNode(targetNodeId, "MorphologicalTree::getPathBetweenNodes");
2049 return PathBetweenNodesRange(this, sourceNodeId, targetNodeId, topologyVersion_);
2050 }
2051
2056 requireAliveNode(nodeId, "MorphologicalTree::getNodeSubtree");
2057 return SubtreeNodeRange(this, nodeId, topologyVersion_);
2058 }
2059
2064 requireAliveNode(nodeId, "MorphologicalTree::getDescendants");
2065 return DescendantNodeRange(this, nodeId, topologyVersion_);
2066 }
2067
2078 [[nodiscard("Discarding the editor leaves the tree edit session open")]] TreeEditor edit();
2079
2087 inline void pruneNode(NodeId nodeId) {
2088 requireNotEditing("MorphologicalTree::pruneNode");
2089 requireAliveNonRootNode(nodeId, "MorphologicalTree::pruneNode");
2092 throw std::invalid_argument("MorphologicalTree::pruneNode requires an attached non-root node.");
2093 }
2095
2096 const auto descendToDeepestLastChild = [this](NodeId startId) {
2098 while (firstChild_[currentId] != InvalidNode) {
2099 currentId = lastChild_[currentId];
2100 }
2101 return currentId;
2102 };
2103
2105 while (true) {
2106 const NodeId currentParentSlotId = nodeParent_[currentId];
2107 moveProperParts(parentSlotId, currentId);
2109 unlinkChildSlot(currentParentSlotId, currentId, false);
2110 }
2111 nodeParent_[currentId] = currentId;
2112 releaseNode(currentId);
2113
2114 if (currentId == nodeId) {
2115 break;
2116 }
2118 }
2119 }
2120
2129 requireNotEditing("MorphologicalTree::mergeNodeIntoParent");
2130 requireAliveNonRootNode(nodeId, "MorphologicalTree::mergeNodeIntoParent");
2133 throw std::invalid_argument("MorphologicalTree::mergeNodeIntoParent requires an attached non-root node.");
2134 }
2136 const NodeId nodeSlotId = nodeId;
2137
2138 spliceProperPartsSlots(parentSlotId, nodeSlotId);
2139 spliceChildrenSlots(parentSlotId, nodeSlotId, ChildSplicePolicy::ReplaceSourceSlotWhenDirectChild);
2140 nodeParent_[nodeSlotId] = nodeSlotId;
2141 bumpProperPartVersion();
2142 releaseNode(nodeId);
2143 }
2144
2145private:
2146 // ========================= Internal classes ========================= //
2186 class LCAEulerRMQ {
2187 private:
2188 std::vector<NodeId> euler_;
2189 std::vector<int> depth_;
2190 std::vector<int> firstOccurrence_;
2191 std::vector<int> log2_;
2192 std::vector<int> sparseTable_;
2193 int sparseTableStride_ = 0;
2194 const MorphologicalTree* tree_ = nullptr;
2195
2199 void depthFirstTraversal(NodeId nodeId, int currentDepth) {
2200 const NodeId slotId = nodeId;
2201 if (firstOccurrence_[static_cast<size_t>(slotId)] == -1) {
2202 firstOccurrence_[static_cast<size_t>(slotId)] = static_cast<int>(euler_.size());
2203 }
2204
2205 euler_.push_back(nodeId);
2206 depth_.push_back(currentDepth);
2207
2208 for (NodeId childNodeId : tree_->getChildren(nodeId)) {
2209 depthFirstTraversal(childNodeId, currentDepth + 1);
2210 euler_.push_back(nodeId);
2211 depth_.push_back(currentDepth);
2212 }
2213 }
2214
2218 void buildSparseTable() {
2219 const int n = static_cast<int>(depth_.size());
2220 if (n == 0) {
2221 log2_.clear();
2222 sparseTable_.clear();
2223 sparseTableStride_ = 0;
2224 return;
2225 }
2226
2227 log2_.assign(static_cast<size_t>(n + 1), 0);
2228 for (int i = 2; i <= n; ++i) {
2229 log2_[static_cast<size_t>(i)] = log2_[static_cast<size_t>(i / 2)] + 1;
2230 }
2231
2232 sparseTableStride_ = 1;
2233 while ((1 << sparseTableStride_) <= n) {
2234 ++sparseTableStride_;
2235 }
2236
2237 sparseTable_.assign(static_cast<size_t>(n) * static_cast<size_t>(sparseTableStride_), 0);
2238
2239 for (int i = 0; i < n; ++i) {
2240 sparseTable_[sparseTableIndex(i, 0)] = i;
2241 }
2242
2243 for (int j = 1; j < sparseTableStride_; ++j) {
2244 const int blockSize = 1 << j;
2245 const int halfBlock = blockSize >> 1;
2246 for (int i = 0; i + blockSize <= n; ++i) {
2247 const int leftIndex = sparseTable_[sparseTableIndex(i, j - 1)];
2248 const int rightIndex = sparseTable_[sparseTableIndex(i + halfBlock, j - 1)];
2249 sparseTable_[sparseTableIndex(i, j)] =
2250 depth_[static_cast<size_t>(leftIndex)] <= depth_[static_cast<size_t>(rightIndex)] ? leftIndex : rightIndex;
2251 }
2252 }
2253 }
2254
2258 std::size_t sparseTableIndex(int row, int col) const {
2259 return static_cast<std::size_t>(row) * static_cast<std::size_t>(sparseTableStride_) +
2260 static_cast<std::size_t>(col);
2261 }
2262
2266 int rmq(int left, int right) const {
2267 const int length = right - left + 1;
2268 const int logLength = log2_[static_cast<size_t>(length)];
2269 const int leftIndex = sparseTable_[sparseTableIndex(left, logLength)];
2270 const int rightIndex = sparseTable_[sparseTableIndex(right - (1 << logLength) + 1, logLength)];
2271 return depth_[static_cast<size_t>(leftIndex)] <= depth_[static_cast<size_t>(rightIndex)] ? leftIndex : rightIndex;
2272 }
2273
2274 public:
2278 explicit LCAEulerRMQ(const MorphologicalTree* tree) : tree_(tree) {
2279 if (tree_ == nullptr || tree_->getRoot() == InvalidNode) {
2280 return;
2281 }
2282
2283 euler_.reserve(static_cast<size_t>(std::max(0, 2 * tree_->getNumNodes() - 1)));
2284 depth_.reserve(static_cast<size_t>(std::max(0, 2 * tree_->getNumNodes() - 1)));
2285 firstOccurrence_.assign(static_cast<size_t>(tree_->getNumInternalNodeSlots()), -1);
2286
2287 depthFirstTraversal(tree_->getRoot(), 0);
2288 buildSparseTable();
2289 }
2290
2294 NodeId findLowestCommonAncestor(NodeId u, NodeId v) const {
2295 const int firstU = firstOccurrence_[static_cast<size_t>(u)];
2296 const int firstV = firstOccurrence_[static_cast<size_t>(v)];
2297 if (firstU < 0 || firstV < 0) {
2298 return InvalidNode;
2299 }
2300
2301 int left = firstU;
2302 int right = firstV;
2303 if (left > right) {
2304 std::swap(left, right);
2305 }
2306 return euler_[static_cast<size_t>(rmq(left, right))];
2307 }
2308 };
2309
2310public:
2311 // ========================= Internal classes and iterators ========================= //
2312
2317 private:
2318 const MorphologicalTree* T_ = nullptr;
2319 NodeId current_ = InvalidNode;
2320 NodeId end_ = InvalidNode;
2321 std::size_t expectedVersion_ = 0;
2322
2326 void settle_() {
2327 if (!T_) {
2328 current_ = InvalidNode;
2329 return;
2330 }
2331 T_->checkNodeIteratorVersion(expectedVersion_);
2332 while (current_ != InvalidNode && current_ < end_ && !T_->isAlive(current_)) {
2333 ++current_;
2334 }
2335 if (current_ >= end_) {
2336 current_ = InvalidNode;
2337 }
2338 }
2339
2340 public:
2342 using iterator_category = std::input_iterator_tag;
2346 using difference_type = std::ptrdiff_t;
2348 using pointer = const NodeId*;
2350 using reference = const NodeId&;
2351
2356
2361 : T_(tree), current_(current), end_(end), expectedVersion_(expectedVersion) {
2362 settle_();
2363 }
2364
2369 T_->checkNodeIteratorVersion(expectedVersion_);
2370 if (current_ != InvalidNode) {
2371 ++current_;
2372 settle_();
2373 }
2374 return *this;
2375 }
2376
2381 T_->checkNodeIteratorVersion(expectedVersion_);
2382 return current_;
2383 }
2384
2388 bool operator==(const AliveNodeIterator& other) const { return current_ == other.current_; }
2389
2393 bool operator!=(const AliveNodeIterator& other) const { return !(*this == other); }
2394 };
2395
2400 private:
2401 const MorphologicalTree* T_ = nullptr;
2402 NodeId begin_ = InvalidNode;
2403 NodeId end_ = InvalidNode;
2404 std::size_t expectedVersion_ = 0;
2405
2406 public:
2410 AliveNodeRange() = default;
2411
2416 : T_(tree), begin_(begin), end_(end), expectedVersion_(expectedVersion) {}
2417
2421 AliveNodeIterator begin() const { return AliveNodeIterator(T_, begin_, end_, expectedVersion_); }
2422
2426 AliveNodeIterator end() const { return AliveNodeIterator(T_, InvalidNode, end_, expectedVersion_); }
2427 };
2428
2433 private:
2434 const MorphologicalTree* T_ = nullptr;
2435 NodeId currentLocal_ = InvalidNode;
2436 std::size_t expectedVersion_ = 0;
2437
2438 public:
2440 using iterator_category = std::forward_iterator_tag;
2444 using difference_type = std::ptrdiff_t;
2446 using pointer = const NodeId*;
2448 using reference = const NodeId&;
2449
2453 ChildrenIterator() = default;
2454
2459 : T_(tree), currentLocal_(currentLocal), expectedVersion_(expectedVersion) {}
2460
2465 T_->checkTopologyIteratorVersion(expectedVersion_);
2466 if (T_ && currentLocal_ != InvalidNode) {
2467 currentLocal_ = T_->nextSibling_[currentLocal_];
2468 }
2469 return *this;
2470 }
2471
2476 T_->checkTopologyIteratorVersion(expectedVersion_);
2477 return currentLocal_;
2478 }
2479
2483 bool operator==(const ChildrenIterator& other) const { return currentLocal_ == other.currentLocal_; }
2484
2488 bool operator!=(const ChildrenIterator& other) const { return !(*this == other); }
2489 };
2490
2495 private:
2496 const MorphologicalTree* T_ = nullptr;
2497 NodeId firstLocal_ = InvalidNode;
2498 std::size_t expectedVersion_ = 0;
2499
2500 public:
2504 ChildrenRange() = default;
2505
2510 : T_(tree), firstLocal_(firstLocal), expectedVersion_(expectedVersion) {}
2511
2515 ChildrenIterator begin() const { return ChildrenIterator(T_, firstLocal_, expectedVersion_); }
2516
2520 ChildrenIterator end() const { return ChildrenIterator(T_, InvalidNode, expectedVersion_); }
2521 };
2522
2527 private:
2528 const MorphologicalTree* T_ = nullptr;
2529 NodeId currentPixel_ = InvalidNode;
2530 std::size_t expectedVersion_ = 0;
2531
2532 public:
2534 using iterator_category = std::forward_iterator_tag;
2538 using difference_type = std::ptrdiff_t;
2540 using pointer = const NodeId*;
2542 using reference = const NodeId&;
2543
2548
2553 : T_(tree), currentPixel_(currentPixel), expectedVersion_(expectedVersion) {}
2554
2559 T_->checkProperPartIteratorVersion(expectedVersion_);
2560 if (T_ && currentPixel_ != InvalidNode) {
2561 currentPixel_ = T_->nextProperPart_[currentPixel_];
2562 }
2563 return *this;
2564 }
2565
2570 T_->checkProperPartIteratorVersion(expectedVersion_);
2571 return currentPixel_;
2572 }
2573
2577 bool operator==(const ProperPartsIterator& other) const { return currentPixel_ == other.currentPixel_; }
2578
2582 bool operator!=(const ProperPartsIterator& other) const { return !(*this == other); }
2583 };
2584
2589 private:
2590 const MorphologicalTree* T_ = nullptr;
2591 NodeId firstPixel_ = InvalidNode;
2592 std::size_t expectedVersion_ = 0;
2593
2594 public:
2598 ProperPartsRange() = default;
2599
2604 : T_(tree), firstPixel_(firstPixel), expectedVersion_(expectedVersion) {}
2605
2609 ProperPartsIterator begin() const { return ProperPartsIterator(T_, firstPixel_, expectedVersion_); }
2610
2614 ProperPartsIterator end() const { return ProperPartsIterator(T_, InvalidNode, expectedVersion_); }
2615 };
2616
2621 private:
2622 const MorphologicalTree* T_ = nullptr;
2623 std::vector<NodeId> nodeStack_;
2624 NodeId currentPixel_ = InvalidNode;
2625 std::size_t expectedTopologyVersion_ = 0;
2626 std::size_t expectedProperPartVersion_ = 0;
2627
2631 void checkVersions() const {
2632 T_->checkTopologyIteratorVersion(expectedTopologyVersion_);
2633 T_->checkProperPartIteratorVersion(expectedProperPartVersion_);
2634 }
2635
2639 void pushChildren(NodeId nodeId) {
2640 std::vector<NodeId> children;
2641 for (NodeId childId : T_->getChildren(nodeId)) {
2642 children.push_back(childId);
2643 }
2644 for (auto it = children.rbegin(); it != children.rend(); ++it) {
2645 nodeStack_.push_back(*it);
2646 }
2647 }
2648
2652 void settle() {
2653 checkVersions();
2654 while (currentPixel_ == InvalidNode && !nodeStack_.empty()) {
2655 const NodeId nodeId = nodeStack_.back();
2656 nodeStack_.pop_back();
2657 pushChildren(nodeId);
2658 currentPixel_ = T_->properHead_[static_cast<size_t>(nodeId)];
2659 }
2660 }
2661
2662 public:
2664 using iterator_category = std::forward_iterator_tag;
2668 using difference_type = std::ptrdiff_t;
2670 using pointer = const NodeId*;
2672 using reference = const NodeId&;
2673
2678
2683 const MorphologicalTree* tree,
2685 std::size_t expectedTopologyVersion,
2686 std::size_t expectedProperPartVersion)
2687 : T_(tree),
2688 expectedTopologyVersion_(expectedTopologyVersion),
2689 expectedProperPartVersion_(expectedProperPartVersion)
2690 {
2691 if (T_ && rootNodeId != InvalidNode) {
2692 nodeStack_.push_back(rootNodeId);
2693 settle();
2694 }
2695 }
2696
2701 checkVersions();
2702 if (currentPixel_ != InvalidNode) {
2703 currentPixel_ = T_->nextProperPart_[static_cast<size_t>(currentPixel_)];
2704 }
2705 settle();
2706 return *this;
2707 }
2708
2713 checkVersions();
2714 return currentPixel_;
2715 }
2716
2720 bool operator==(const ConnectedComponentIterator& other) const { return currentPixel_ == other.currentPixel_; }
2721
2725 bool operator!=(const ConnectedComponentIterator& other) const { return !(*this == other); }
2726 };
2727
2732 private:
2733 const MorphologicalTree* T_ = nullptr;
2734 NodeId rootNodeId_ = InvalidNode;
2735 std::size_t expectedTopologyVersion_ = 0;
2736 std::size_t expectedProperPartVersion_ = 0;
2737
2738 public:
2743
2748 const MorphologicalTree* tree,
2750 std::size_t expectedTopologyVersion,
2751 std::size_t expectedProperPartVersion)
2752 : T_(tree),
2753 rootNodeId_(rootNodeId),
2754 expectedTopologyVersion_(expectedTopologyVersion),
2755 expectedProperPartVersion_(expectedProperPartVersion) {}
2756
2761 return ConnectedComponentIterator(T_, rootNodeId_, expectedTopologyVersion_, expectedProperPartVersion_);
2762 }
2763
2768 };
2769
2774 private:
2775 struct Item { NodeId id; bool expanded; };
2776 const MorphologicalTree* T_ = nullptr;
2777 std::vector<Item> stack_;
2778 NodeId current_ = InvalidNode;
2779 std::size_t expectedVersion_ = 0;
2780
2784 void settle_() {
2785 T_->checkTopologyIteratorVersion(expectedVersion_);
2786 while (!stack_.empty()) {
2787 Item& top = stack_.back();
2788 if (!top.expanded) {
2789 top.expanded = true;
2790 std::vector<NodeId> children;
2791 for (NodeId childId : T_->getChildren(top.id)) {
2792 children.push_back(childId);
2793 }
2794 for (auto it = children.rbegin(); it != children.rend(); ++it) {
2795 stack_.push_back({*it, false});
2796 }
2797 } else {
2798 current_ = top.id;
2799 return;
2800 }
2801 }
2802 current_ = InvalidNode;
2803 }
2804
2805 public:
2807 using iterator_category = std::input_iterator_tag;
2811 using difference_type = std::ptrdiff_t;
2813 using pointer = const NodeId*;
2815 using reference = const NodeId&;
2816
2821
2825 PostOrderNodeIterator(const MorphologicalTree* tree, NodeId rootNodeId, std::size_t expectedVersion) : T_(tree), expectedVersion_(expectedVersion) {
2826 if (T_ && rootNodeId != InvalidNode) {
2827 stack_.push_back({rootNodeId, false});
2828 settle_();
2829 }
2830 }
2831
2836 T_->checkTopologyIteratorVersion(expectedVersion_);
2837 if (!stack_.empty()) {
2838 stack_.pop_back();
2839 settle_();
2840 }
2841 return *this;
2842 }
2843
2848 T_->checkTopologyIteratorVersion(expectedVersion_);
2849 return current_;
2850 }
2851
2855 bool operator==(const PostOrderNodeIterator& other) const { return current_ == other.current_; }
2856
2860 bool operator!=(const PostOrderNodeIterator& other) const { return !(*this == other); }
2861 };
2862
2867 private:
2868 const MorphologicalTree* T_ = nullptr;
2869 NodeId rootNodeId_ = InvalidNode;
2870 std::size_t expectedVersion_ = 0;
2871
2872 public:
2877
2882 : T_(tree), rootNodeId_(rootNodeId), expectedVersion_(expectedVersion) {}
2883
2887 PostOrderNodeIterator begin() const { return PostOrderNodeIterator(T_, rootNodeId_, expectedVersion_); }
2888
2893 };
2894
2899 private:
2900 const MorphologicalTree* T_ = nullptr;
2901 FastQueue<NodeId> queue_;
2902 std::size_t expectedVersion_ = 0;
2903
2904 public:
2906 using iterator_category = std::input_iterator_tag;
2910 using difference_type = std::ptrdiff_t;
2912 using pointer = const NodeId*;
2914 using reference = const NodeId&;
2915
2920
2924 BreadthFirstNodeIterator(const MorphologicalTree* tree, NodeId rootNodeId, std::size_t expectedVersion) : T_(tree), expectedVersion_(expectedVersion) {
2925 if (T_ && rootNodeId != InvalidNode) {
2926 queue_.push(rootNodeId);
2927 }
2928 }
2929
2934 T_->checkTopologyIteratorVersion(expectedVersion_);
2935 if (!queue_.empty()) {
2936 NodeId current = queue_.pop();
2937 for (NodeId childId : T_->getChildren(current)) {
2938 queue_.push(childId);
2939 }
2940 }
2941 return *this;
2942 }
2943
2948 T_->checkTopologyIteratorVersion(expectedVersion_);
2949 return queue_.front();
2950 }
2951
2955 bool operator==(const BreadthFirstNodeIterator& other) const { return queue_.empty() == other.queue_.empty(); }
2956
2960 bool operator!=(const BreadthFirstNodeIterator& other) const { return !(*this == other); }
2961 };
2962
2967 private:
2968 const MorphologicalTree* T_ = nullptr;
2969 NodeId rootNodeId_ = InvalidNode;
2970 std::size_t expectedVersion_ = 0;
2971
2972 public:
2977
2982 : T_(tree), rootNodeId_(rootNodeId), expectedVersion_(expectedVersion) {}
2983
2987 BreadthFirstNodeIterator begin() const { return BreadthFirstNodeIterator(T_, rootNodeId_, expectedVersion_); }
2988
2993 };
2994
2999 private:
3000 const MorphologicalTree* T_ = nullptr;
3001 NodeId current_ = InvalidNode;
3002 std::size_t expectedVersion_ = 0;
3003
3004 public:
3006 using iterator_category = std::input_iterator_tag;
3010 using difference_type = std::ptrdiff_t;
3012 using pointer = const NodeId*;
3014 using reference = const NodeId&;
3015
3020
3025 : T_(tree), current_(current), expectedVersion_(expectedVersion) {}
3026
3031 T_->checkTopologyIteratorVersion(expectedVersion_);
3032 if (T_ && current_ != InvalidNode) {
3033 if (T_->isRoot(current_)) {
3034 current_ = InvalidNode;
3035 } else {
3036 current_ = T_->getNodeParent(current_);
3037 }
3038 }
3039 return *this;
3040 }
3041
3046 T_->checkTopologyIteratorVersion(expectedVersion_);
3047 return current_;
3048 }
3049
3053 bool operator==(const PathToRootIterator& other) const { return current_ == other.current_; }
3054
3058 bool operator!=(const PathToRootIterator& other) const { return !(*this == other); }
3059 };
3060
3065 private:
3066 const MorphologicalTree* T_ = nullptr;
3067 NodeId start_ = InvalidNode;
3068 std::size_t expectedVersion_ = 0;
3069
3070 public:
3074 PathToRootRange() = default;
3075
3080 : T_(tree), start_(start), expectedVersion_(expectedVersion) {}
3081
3085 PathToRootIterator begin() const { return PathToRootIterator(T_, start_, expectedVersion_); }
3086
3091 };
3092
3097 private:
3098 const MorphologicalTree* T_ = nullptr;
3099 const std::vector<NodeId>* path_ = nullptr;
3100 std::size_t index_ = 0;
3101 std::size_t expectedVersion_ = 0;
3102
3103 public:
3105 using iterator_category = std::input_iterator_tag;
3109 using difference_type = std::ptrdiff_t;
3111 using pointer = const NodeId*;
3113 using reference = const NodeId&;
3114
3119
3124 const MorphologicalTree* tree,
3125 const std::vector<NodeId>* path,
3126 std::size_t index,
3127 std::size_t expectedVersion)
3128 : T_(tree), path_(path), index_(index), expectedVersion_(expectedVersion) {}
3129
3134 T_->checkTopologyIteratorVersion(expectedVersion_);
3135 if (path_ && index_ < path_->size()) {
3136 ++index_;
3137 }
3138 return *this;
3139 }
3140
3145 T_->checkTopologyIteratorVersion(expectedVersion_);
3146 return (*path_)[index_];
3147 }
3148
3153 return path_ == other.path_ && index_ == other.index_;
3154 }
3155
3159 bool operator!=(const PathBetweenNodesIterator& other) const { return !(*this == other); }
3160 };
3161
3166 private:
3167 const MorphologicalTree* T_ = nullptr;
3168 std::vector<NodeId> path_;
3169 std::size_t expectedVersion_ = 0;
3170
3174 static std::vector<NodeId> buildPath(const MorphologicalTree* tree, NodeId sourceNodeId, NodeId targetNodeId) {
3175 if (tree == nullptr || !tree->isAlive(sourceNodeId) || !tree->isAlive(targetNodeId)) {
3176 return {};
3177 }
3178
3179 auto componentAnchor = [tree](NodeId nodeId) {
3181 while (true) {
3184 return currentNodeId;
3185 }
3187 }
3188 };
3189
3191 return {};
3192 }
3193
3195 if (lcaNodeId == InvalidNode) {
3196 return {};
3197 }
3198
3199 std::vector<NodeId> path;
3201 path.push_back(currentNodeId);
3202 if (currentNodeId == lcaNodeId) {
3203 break;
3204 }
3205
3208 return {};
3209 }
3210 }
3211
3212 std::vector<NodeId> descendingTail;
3214 descendingTail.push_back(currentNodeId);
3215
3218 return {};
3219 }
3220 }
3221
3222 path.insert(path.end(), descendingTail.rbegin(), descendingTail.rend());
3223 return path;
3224 }
3225
3226 public:
3231
3236 const MorphologicalTree* tree,
3239 std::size_t expectedVersion)
3240 : T_(tree),
3241 path_(buildPath(tree, sourceNodeId, targetNodeId)),
3242 expectedVersion_(expectedVersion) {}
3243
3248 return PathBetweenNodesIterator(T_, &path_, 0, expectedVersion_);
3249 }
3250
3255 return PathBetweenNodesIterator(T_, &path_, path_.size(), expectedVersion_);
3256 }
3257 };
3258
3263 private:
3264 const MorphologicalTree* T_ = nullptr;
3265 std::vector<NodeId> stack_;
3266 std::size_t expectedVersion_ = 0;
3267
3268 public:
3270 using iterator_category = std::input_iterator_tag;
3274 using difference_type = std::ptrdiff_t;
3276 using pointer = const NodeId*;
3278 using reference = const NodeId&;
3279
3284
3288 SubtreeNodeIterator(const MorphologicalTree* tree, NodeId rootNodeId, std::size_t expectedVersion) : T_(tree), expectedVersion_(expectedVersion) {
3289 if (T_ && rootNodeId != InvalidNode) {
3290 stack_.push_back(rootNodeId);
3291 }
3292 }
3293
3298 T_->checkTopologyIteratorVersion(expectedVersion_);
3299 if (!stack_.empty()) {
3300 NodeId current = stack_.back();
3301 stack_.pop_back();
3302 std::vector<NodeId> children;
3303 for (NodeId childId : T_->getChildren(current)) {
3304 children.push_back(childId);
3305 }
3306 for (auto it = children.rbegin(); it != children.rend(); ++it) {
3307 stack_.push_back(*it);
3308 }
3309 }
3310 return *this;
3311 }
3312
3317 T_->checkTopologyIteratorVersion(expectedVersion_);
3318 return stack_.back();
3319 }
3320
3324 bool operator==(const SubtreeNodeIterator& other) const { return stack_.empty() == other.stack_.empty(); }
3325
3329 bool operator!=(const SubtreeNodeIterator& other) const { return !(*this == other); }
3330 };
3331
3336 private:
3337 const MorphologicalTree* T_ = nullptr;
3338 NodeId rootNodeId_ = InvalidNode;
3339 std::size_t expectedVersion_ = 0;
3340
3341 public:
3345 SubtreeNodeRange() = default;
3346
3351 : T_(tree), rootNodeId_(rootNodeId), expectedVersion_(expectedVersion) {}
3352
3356 SubtreeNodeIterator begin() const { return SubtreeNodeIterator(T_, rootNodeId_, expectedVersion_); }
3357
3362 };
3363
3368 private:
3369 const MorphologicalTree* T_ = nullptr;
3370 NodeId rootNodeId_ = InvalidNode;
3371 std::size_t expectedVersion_ = 0;
3372
3373 public:
3378
3383 : T_(tree), rootNodeId_(rootNodeId), expectedVersion_(expectedVersion) {}
3384
3389 auto it = SubtreeNodeIterator(T_, rootNodeId_, expectedVersion_);
3390 ++it;
3391 return it;
3392 }
3393
3398 };
3399
3400
3401
3402};
3403
3404} // namespace mmcfilters
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:25
std::shared_ptr< Image< PixelType > > ImagePtr
Shared pointer alias for an image with arbitrary pixel type.
Definition Image.hpp:253
std::shared_ptr< ImageUInt8 > ImageUInt8Ptr
Shared pointer to an 8-bit unsigned image.
Definition Image.hpp:243
Two-dimensional adjacency relation with configurable radius and efficient iteration.
Iterator over live node ids in the dense internal-node domain.
AliveNodeIterator(const MorphologicalTree *tree, NodeId current, NodeId end, std::size_t expectedVersion)
Creates an iterator over [current, end) with fail-fast version checking.
AliveNodeIterator & operator++()
Advances to the next live node slot.
NodeId operator*() const
Returns the current live node id.
bool operator!=(const AliveNodeIterator &other) const
Compares iterator positions for inequality.
AliveNodeIterator()=default
Creates the end/sentinel iterator.
bool operator==(const AliveNodeIterator &other) const
Compares iterator positions.
std::ptrdiff_t difference_type
Signed distance type exposed for STL compatibility.
std::input_iterator_tag iterator_category
Standard iterator category exposed for STL compatibility.
Range wrapper for iterating over live node ids.
AliveNodeRange(const MorphologicalTree *tree, NodeId begin, NodeId end, std::size_t expectedVersion)
Creates a fail-fast live-node range over dense slots.
AliveNodeIterator begin() const
Returns an iterator positioned at the first live slot.
AliveNodeIterator end() const
Returns the sentinel iterator for the live-node range.
AliveNodeRange()=default
Creates an empty live-node range.
std::ptrdiff_t difference_type
Signed distance type exposed for STL compatibility.
bool operator==(const BreadthFirstNodeIterator &other) const
Compares breadth-first iterator exhaustion state.
bool operator!=(const BreadthFirstNodeIterator &other) const
Compares breadth-first iterator exhaustion state for inequality.
BreadthFirstNodeIterator(const MorphologicalTree *tree, NodeId rootNodeId, std::size_t expectedVersion)
Creates a breadth-first iterator rooted at rootNodeId.
BreadthFirstNodeIterator & operator++()
Advances to the next node in breadth-first order.
BreadthFirstNodeIterator()=default
Creates the end/sentinel breadth-first iterator.
NodeId operator*() const
Returns the current breadth-first node id.
std::input_iterator_tag iterator_category
Standard iterator category exposed for STL compatibility.
Range wrapper for breadth-first subtree traversal.
BreadthFirstNodeIterator end() const
Returns the breadth-first range sentinel.
BreadthFirstNodeIterator begin() const
Returns an iterator at the first breadth-first node.
BreadthFirstNodeRange()=default
Creates an empty breadth-first range.
BreadthFirstNodeRange(const MorphologicalTree *tree, NodeId rootNodeId, std::size_t expectedVersion)
Creates a breadth-first range rooted at rootNodeId.
Iterator over the direct children of one node.
ChildrenIterator()=default
Creates the end/sentinel child iterator.
ChildrenIterator(const MorphologicalTree *tree, NodeId currentLocal, std::size_t expectedVersion)
Creates a child iterator starting from a linked-list slot.
bool operator==(const ChildrenIterator &other) const
Compares child iterator positions.
NodeId operator*() const
Returns the current child node id.
std::ptrdiff_t difference_type
Signed distance type exposed for STL compatibility.
bool operator!=(const ChildrenIterator &other) const
Compares child iterator positions for inequality.
ChildrenIterator & operator++()
Advances to the next sibling in the child list.
std::forward_iterator_tag iterator_category
Standard iterator category exposed for STL compatibility.
Range wrapper for direct-child iteration.
ChildrenIterator end() const
Returns the child-range sentinel iterator.
ChildrenIterator begin() const
Returns an iterator at the first child.
ChildrenRange()=default
Creates an empty child range.
ChildrenRange(const MorphologicalTree *tree, NodeId firstLocal, std::size_t expectedVersion)
Creates a range over a linked list of direct children.
Iterator over all proper parts in one connected component.
NodeId operator*() const
Returns the current proper-part id.
ConnectedComponentIterator & operator++()
Advances to the next proper part in the connected component.
ConnectedComponentIterator(const MorphologicalTree *tree, NodeId rootNodeId, std::size_t expectedTopologyVersion, std::size_t expectedProperPartVersion)
Creates an iterator over every proper part in a rooted subtree.
bool operator==(const ConnectedComponentIterator &other) const
Compares connected-component iterator positions.
std::forward_iterator_tag iterator_category
Standard iterator category exposed for STL compatibility.
std::ptrdiff_t difference_type
Signed distance type exposed for STL compatibility.
ConnectedComponentIterator()=default
Creates the end/sentinel connected-component iterator.
bool operator!=(const ConnectedComponentIterator &other) const
Compares connected-component iterator positions for inequality.
Range wrapper for connected-component proper-part iteration.
ConnectedComponentRange()=default
Creates an empty connected-component range.
ConnectedComponentIterator begin() const
Returns an iterator at the first proper part in the component.
ConnectedComponentIterator end() const
Returns the connected-component range sentinel.
ConnectedComponentRange(const MorphologicalTree *tree, NodeId rootNodeId, std::size_t expectedTopologyVersion, std::size_t expectedProperPartVersion)
Creates a range over all proper parts in the subtree of rootNodeId.
Range wrapper over the proper descendants of one node.
DescendantNodeRange(const MorphologicalTree *tree, NodeId rootNodeId, std::size_t expectedVersion)
Creates a range over proper descendants of rootNodeId.
DescendantNodeRange()=default
Creates an empty descendant range.
SubtreeNodeIterator begin() const
Returns an iterator at the first proper descendant.
SubtreeNodeIterator end() const
Returns the descendant range sentinel.
Iterator over a materialised path between two nodes.
bool operator==(const PathBetweenNodesIterator &other) const
Compares path iterator positions.
PathBetweenNodesIterator()=default
Creates the end/sentinel path-between-nodes iterator.
std::input_iterator_tag iterator_category
Standard iterator category exposed for STL compatibility.
PathBetweenNodesIterator & operator++()
Advances to the next node in the materialised path.
bool operator!=(const PathBetweenNodesIterator &other) const
Compares path iterator positions for inequality.
std::ptrdiff_t difference_type
Signed distance type exposed for STL compatibility.
NodeId operator*() const
Returns the current node id in the materialised path.
PathBetweenNodesIterator(const MorphologicalTree *tree, const std::vector< NodeId > *path, std::size_t index, std::size_t expectedVersion)
Creates an iterator over a materialised node path.
Range wrapper for the path connecting two nodes in the same component.
PathBetweenNodesIterator begin() const
Returns an iterator at the first node in the materialised path.
PathBetweenNodesRange()=default
Creates an empty path-between-nodes range.
PathBetweenNodesIterator end() const
Returns the materialised path range sentinel.
PathBetweenNodesRange(const MorphologicalTree *tree, NodeId sourceNodeId, NodeId targetNodeId, std::size_t expectedVersion)
Materialises and owns the path between two nodes.
Iterator that walks from a node towards the root.
bool operator!=(const PathToRootIterator &other) const
Compares path-to-root iterator positions for inequality.
std::input_iterator_tag iterator_category
Standard iterator category exposed for STL compatibility.
bool operator==(const PathToRootIterator &other) const
Compares path-to-root iterator positions.
std::ptrdiff_t difference_type
Signed distance type exposed for STL compatibility.
NodeId operator*() const
Returns the current node on the rootward path.
PathToRootIterator & operator++()
Advances one step toward the root.
PathToRootIterator(const MorphologicalTree *tree, NodeId current, std::size_t expectedVersion)
Creates an iterator starting at one node and walking to the root.
PathToRootIterator()=default
Creates the end/sentinel rootward-path iterator.
Range wrapper for rootward path traversal.
PathToRootRange(const MorphologicalTree *tree, NodeId start, std::size_t expectedVersion)
Creates a range from start to the connected root.
PathToRootIterator end() const
Returns the rootward-path range sentinel.
PathToRootIterator begin() const
Returns an iterator at the path start node.
PathToRootRange()=default
Creates an empty rootward-path range.
bool operator==(const PostOrderNodeIterator &other) const
Compares post-order iterator positions.
PostOrderNodeIterator(const MorphologicalTree *tree, NodeId rootNodeId, std::size_t expectedVersion)
Creates a post-order iterator rooted at rootNodeId.
PostOrderNodeIterator & operator++()
Advances to the next node in post-order.
NodeId operator*() const
Returns the current post-order node id.
std::input_iterator_tag iterator_category
Standard iterator category exposed for STL compatibility.
std::ptrdiff_t difference_type
Signed distance type exposed for STL compatibility.
bool operator!=(const PostOrderNodeIterator &other) const
Compares post-order iterator positions for inequality.
PostOrderNodeIterator()=default
Creates the end/sentinel post-order iterator.
Range wrapper for post-order subtree traversal.
PostOrderNodeRange(const MorphologicalTree *tree, NodeId rootNodeId, std::size_t expectedVersion)
Creates a post-order range rooted at rootNodeId.
PostOrderNodeIterator begin() const
Returns an iterator at the first post-order node.
PostOrderNodeIterator end() const
Returns the post-order range sentinel.
PostOrderNodeRange()=default
Creates an empty post-order range.
Iterator over the direct proper parts owned by one node.
ProperPartsIterator & operator++()
Advances to the next proper part in the owner list.
NodeId operator*() const
Returns the current proper-part id.
ProperPartsIterator()=default
Creates the end/sentinel proper-part iterator.
ProperPartsIterator(const MorphologicalTree *tree, NodeId currentPixel, std::size_t expectedVersion)
Creates a proper-part iterator starting from a linked-list entry.
bool operator!=(const ProperPartsIterator &other) const
Compares proper-part iterator positions for inequality.
std::forward_iterator_tag iterator_category
Standard iterator category exposed for STL compatibility.
std::ptrdiff_t difference_type
Signed distance type exposed for STL compatibility.
bool operator==(const ProperPartsIterator &other) const
Compares proper-part iterator positions.
Range wrapper for direct proper-part iteration.
ProperPartsRange()=default
Creates an empty direct proper-part range.
ProperPartsIterator end() const
Returns the direct proper-part range sentinel.
ProperPartsRange(const MorphologicalTree *tree, NodeId firstPixel, std::size_t expectedVersion)
Creates a range over one node's direct proper-part list.
ProperPartsIterator begin() const
Returns an iterator at the first direct proper part.
Depth-first iterator over a subtree in pre-order.
SubtreeNodeIterator & operator++()
Advances to the next node in pre-order subtree traversal.
std::input_iterator_tag iterator_category
Standard iterator category exposed for STL compatibility.
bool operator!=(const SubtreeNodeIterator &other) const
Compares subtree iterator exhaustion state for inequality.
bool operator==(const SubtreeNodeIterator &other) const
Compares subtree iterator exhaustion state.
SubtreeNodeIterator(const MorphologicalTree *tree, NodeId rootNodeId, std::size_t expectedVersion)
Creates a pre-order subtree iterator rooted at rootNodeId.
NodeId operator*() const
Returns the current pre-order subtree node id.
std::ptrdiff_t difference_type
Signed distance type exposed for STL compatibility.
SubtreeNodeIterator()=default
Creates the end/sentinel subtree iterator.
Range wrapper for pre-order subtree traversal.
SubtreeNodeIterator end() const
Returns the subtree range sentinel.
SubtreeNodeRange(const MorphologicalTree *tree, NodeId rootNodeId, std::size_t expectedVersion)
Creates a pre-order range over the subtree rooted at rootNodeId.
SubtreeNodeRange()=default
Creates an empty subtree range.
SubtreeNodeIterator begin() const
Returns an iterator at the subtree root.
Mutable morphological tree built directly on proper parts and dense node ids.
int getNumTotalProperParts() const
Returns the size of the proper-part domain.
DescendantNodeRange getDescendants(NodeId nodeId) const
Returns a range over all proper descendants of nodeId.
bool isNode(NodeId nodeId) const noexcept
Tests whether nodeId belongs to the internal-node id domain.
TreeEditor edit()
Opens the only public entrypoint for staged structural mutations.
ChildrenRange getChildren(NodeId nodeId) const
Returns a fail-fast range over the direct children of nodeId.
int getNumLeafNodes() const
Counts the live nodes that currently have no children.
bool hasAdjacencyRelation() const noexcept
Tests whether an adjacency relation is attached to the tree.
bool isLeaf(NodeId nodeId) const
Tests whether nodeId has no direct children.
void requireMutationVersion(std::size_t expectedVersion, const char *context) const
Rejects stale read-only views that captured an older mutation version.
double getTreeOfShapesMinTreeAdjacencyRadius() const
Returns the auxiliary min-tree adjacency radius used by the ToS interpolation policy.
bool isStrictComparable(NodeId u, NodeId v) const
Tests whether u and v are strictly comparable in the ancestry order.
MorphologicalTree(MorphologicalTree &&) noexcept=default
Moving transfers the complete topology state.
int getNumInternalNodeSlots() const
Returns the size of the dense internal-node id domain.
NodeId getNextSibling(NodeId nodeId) const
Returns the next sibling of nodeId, or InvalidNode.
bool isAlive(NodeId nodeId) const
Tests whether a node slot currently represents a live node.
int getNumFreeNodeSlots() const
Returns the number of currently reusable node slots.
std::vector< NodeId > getLeaves() const
Returns all live leaf nodes in the current hierarchy.
void validateConnectedRootedTree() const
Validates that the current structure is one connected rooted tree.
const AdjacencyRelation * getTreeOfShapesMinTreeAdjacencyRelation() const noexcept
Returns the auxiliary min-tree adjacency relation used by the ToS interpolation policy.
MorphologicalTree & operator=(const MorphologicalTree &)=delete
Copy assignment is disabled to keep topology ownership explicit.
NodeId getHigraNodeId(NodeId nodeId) const noexcept
Returns the preserved imported Higra node id for one live tree node.
SubtreeNodeRange getNodeSubtree(NodeId nodeId) const
Returns a pre-order traversal range over the subtree of nodeId.
double getTreeOfShapesMaxTreeAdjacencyRadius() const
Returns the auxiliary max-tree adjacency radius used by the ToS interpolation policy.
bool isComparable(NodeId u, NodeId v) const
Tests whether u and v are comparable in the ancestry order.
int getNodeTimePreOrder(NodeId nodeId) const
Returns the preorder time of nodeId in the cached DFS traversal.
bool isEditing() const noexcept
Tests whether a staged edit session is currently open.
BreadthFirstNodeRange getIteratorBreadthFirstTraversal() const
Returns a breadth-first traversal range rooted at the connected root.
MorphologicalTree clone() const
Creates an independent copy of the structural tree state.
void pruneNode(NodeId nodeId)
Prunes the subtree of nodeId, moving all its support to the parent.
bool isRoot(NodeId nodeId) const
Tests whether nodeId is the current root.
int getNumHigraNodes() const
Returns the size of the preserved imported Higra node-id domain.
NodeId getLowestCommonAncestor(NodeId u, NodeId v) const
Returns the lowest common ancestor of u and v.
NodeId getRoot() const
Returns the current hierarchy root.
bool isProperPart(NodeId id) const noexcept
Tests whether id belongs to the proper-part domain.
BreadthFirstNodeRange getIteratorBreadthFirstTraversal(NodeId rootNodeId) const
Returns a breadth-first traversal range rooted at rootNodeId.
MorphologicalTree(detail::MorphologicalTreeConstructionTag, std::span< const NodeId > parent, int rows, int cols, MorphologicalTreeKind kind, std::optional< AdjacencyRelation > adjacency)
Tag-protected import from compact Higra parent representation.
int getNumProperParts(NodeId nodeId) const
Returns the number of direct proper parts owned by nodeId.
bool hasChild(NodeId parentNodeId, NodeId childId) const
Tests whether childId is a direct child of parentNodeId.
NodeId getProperPartOwner(NodeId properPartId) const
Returns the live node that directly owns properPartId.
ProperPartsRange getProperParts(NodeId nodeId) const
Returns a fail-fast range over the direct proper parts of nodeId.
const AdjacencyRelation * getTreeOfShapesMaxTreeAdjacencyRelation() const noexcept
Returns the auxiliary max-tree adjacency relation used by the ToS interpolation policy.
int getNumChildren(NodeId nodeId) const
Returns the number of direct children of nodeId.
bool isStrictAncestor(NodeId u, NodeId v) const
Tests whether u is a strict ancestor of v.
PostOrderNodeRange getPostOrderNodes(NodeId rootNodeId) const
Returns a post-order traversal range rooted at rootNodeId.
const AdjacencyRelation * getAdjacencyRelation() const noexcept
Returns the read-only adjacency relation attached to the tree, if any.
PathBetweenNodesRange getPathBetweenNodes(NodeId sourceNodeId, NodeId targetNodeId) const
Returns the path that connects sourceNodeId and targetNodeId.
bool hasTreeOfShapesAdjacencyPolicy() const noexcept
Tests whether this image-built tree of shapes carries min/max auxiliary adjacency metadata.
PathToRootRange getPathToRootNodes(NodeId nodeId) const
Returns the path from nodeId to the connected root.
std::size_t getMutationVersion() const noexcept
Returns the monotonic mutation counter used by read-only views.
PostOrderNodeRange getPostOrderNodes() const
Returns a post-order traversal range rooted at the connected root.
MorphologicalTreeKind getTreeType() const noexcept
Returns the current tree type.
int getNodeIdSpaceSize(NodeIdSpace outputSpace) const
Returns the size of the requested node-id domain.
int getNumColsOfImage() const
Returns the number of image columns in the attached 2D domain.
bool hasDetachedAliveNodes() const noexcept
Returns whether the tree currently contains alive detached nodes.
TreeValidationResult validateConnectedRootedTreeResult() const noexcept
Runs strong validation and returns the result instead of throwing.
int getNodeTimePostOrder(NodeId nodeId) const
Returns the postorder time of nodeId in the cached DFS traversal.
bool isDescendant(NodeId u, NodeId v) const
Tests whether u is a descendant of v.
bool isAncestor(NodeId u, NodeId v) const
Tests whether u is an ancestor of v.
MorphologicalTree(detail::MorphologicalTreeConstructionTag, ImageUInt8Ptr imgPtr, ToSInterpolation interpolation, int infinitySeedRow, int infinitySeedCol)
Tag-protected constructor for image-based tree-of-shapes topology.
void requireNotEditing(const char *context) const
Rejects operations that require a committed connected topology.
AliveNodeRange getAliveNodeIds() const
Returns a fail-fast range over all live node ids.
int getNodeNumDescendants(NodeId nodeId) const
Returns the number of internal descendants of nodeId.
NodeId getFirstChild(NodeId nodeId) const
Returns the first direct child of nodeId, or InvalidNode.
int getNodeNumSiblings(NodeId nodeId) const
Returns the number of siblings of nodeId.
NodeId getNodeParent(NodeId nodeId) const
Returns the direct parent of nodeId.
int getNumRowsOfImage() const
Returns the number of image rows in the attached 2D domain.
int getNumNodes() const noexcept
Returns the number of currently live nodes.
AdjacencyRelation * getAdjacencyRelation() noexcept
Returns the mutable adjacency relation attached to the tree, if any.
ConnectedComponentRange getConnectedComponent(NodeId nodeId) const
Returns a fail-fast range over all proper parts in the connected component represented by nodeId.
void mergeNodeIntoParent(NodeId nodeId)
Merges nodeId into its parent and releases the emptied slot.
MorphologicalTree(detail::MorphologicalTreeConstructionTag, std::span< const NodeId > nodeParent, std::span< const NodeId > properPartOwner, NodeId root, int rows, int cols)
Tag-protected import from native MAF topology buffers.
bool isStrictDescendant(NodeId u, NodeId v) const
Tests whether u is a strict descendant of v.
MorphologicalTree(const MorphologicalTree &)=delete
Copying is disabled to keep topology ownership explicit.
Thin edit-session facade for multi-step topology updates.
Value types accepted by generic altitude helpers.
Definition Altitude.hpp:42
Owning result for one computed scalar attribute layout and buffer.
std::vector< Real > second
Flat per-node attribute buffer indexed through first.
AttributeNames first
Layout used to interpret second; kept public for tuple-like access.
Non-throwing validation result returned by edit-session checks.
std::string message
Human-readable diagnostic, especially useful when ok is false.