mmcfilters
Public API documentation
Loading...
Searching...
No Matches
DualMinMaxTreeIncrementalFilter.hpp
1#pragma once
2
3#include "DynamicTreeAttributeComputer.hpp"
4#include "../ValuedMorphologicalTree.hpp"
5#include "../../utils/GenerationStampSet.hpp"
6
7#include <algorithm>
8#include <array>
9#include <cassert>
10#include <cstddef>
11#include <cstdint>
12#include <limits>
13#include <map>
14#include <type_traits>
15#include <utility>
16#include <vector>
17
18#ifndef MMCFILTERS_COMPONENT_TREE_ADJUSTMENT_DENSE_MAX_BITS
19#define MMCFILTERS_COMPONENT_TREE_ADJUSTMENT_DENSE_MAX_BITS 8
20#endif
21
22namespace mmcfilters::adjust {
23
107template <AltitudeValue altitude_t> class DualMinMaxTreeIncrementalFilter {
108 private:
115
124 static constexpr bool usesDenseLevels() {
125 if constexpr (std::is_integral_v<altitude_t>) {
126 using unsigned_altitude_t = std::make_unsigned_t<altitude_t>;
127 return std::numeric_limits<unsigned_altitude_t>::digits <= MMCFILTERS_COMPONENT_TREE_ADJUSTMENT_DENSE_MAX_BITS;
128 } else {
129 return false;
130 }
131 }
132
136 static constexpr bool use_dense_levels = usesDenseLevels();
137
147 class MergedNodesCollection {
148 private:
149 template <bool dense, typename AltitudeType> struct StorageSelector;
150
154 template <typename AltitudeType> struct StorageSelector<true, AltitudeType> {
156 static constexpr std::size_t domain_size = static_cast<std::size_t>(static_cast<long long>(std::numeric_limits<AltitudeType>::max()) -
157 static_cast<long long>(std::numeric_limits<AltitudeType>::lowest()) + 1);
159 using type = std::array<std::vector<NodeId>, domain_size>;
160 };
161
165 template <typename AltitudeType> struct StorageSelector<false, AltitudeType> {
167 using type = std::map<AltitudeType, std::vector<NodeId>>;
168 };
169
171 using storage_t = typename StorageSelector<use_dense_levels, altitude_t>::type;
172
174 storage_t mergeNodesByLevelStorage_;
176 std::vector<altitude_t> mergeLevels_;
178 std::vector<NodeId> frontierNodesAboveB_;
180 GenerationStampSet collectedNodeMarks_;
182 GenerationStampSet mergeBucketNodeMarks_;
184 GenerationStampSet adjacentSeedMarks_;
186 std::size_t maxBucketSize_ = 0;
188 int currentMergeLevelIndex_ = 0;
190 bool isMaxtree_ = false;
191
192 public:
201 static std::size_t denseBucketIndex(altitude_t level) {
202 if constexpr (std::is_signed_v<altitude_t>) {
203 return static_cast<std::size_t>(static_cast<long long>(level) - static_cast<long long>(std::numeric_limits<altitude_t>::lowest()));
204 } else {
205 return static_cast<std::size_t>(level);
206 }
207 }
208
215 static altitude_t levelFromDenseBucketIndex(std::size_t index) {
216 if constexpr (std::is_signed_v<altitude_t>) {
217 return static_cast<altitude_t>(static_cast<long long>(index) + static_cast<long long>(std::numeric_limits<altitude_t>::lowest()));
218 } else {
219 return static_cast<altitude_t>(index);
220 }
221 }
222
227 explicit MergedNodesCollection(int maxNodes = 0)
228 : collectedNodeMarks_(static_cast<size_t>(std::max(maxNodes, 0))), mergeBucketNodeMarks_(static_cast<size_t>(std::max(maxNodes, 0))),
229 adjacentSeedMarks_(static_cast<size_t>(std::max(maxNodes, 0))) {}
230
235 void resetCollection(bool isMaxtree) {
236 isMaxtree_ = isMaxtree;
237 for (altitude_t level : mergeLevels_) {
238 if constexpr (use_dense_levels) {
239 // Dense backend reuses one bucket per altitude value and
240 // clears only levels that were active in the previous step.
241 mergeNodesByLevelStorage_[denseBucketIndex(level)].clear();
242 } else {
243 // Sparse backend stores only touched levels, so only those
244 // buckets need to be cleared here.
245 auto it = mergeNodesByLevelStorage_.find(level);
246 if (it != mergeNodesByLevelStorage_.end()) {
247 it->second.clear();
248 }
249 }
250 }
251 mergeLevels_.clear();
252 frontierNodesAboveB_.clear();
253 collectedNodeMarks_.resetAll();
254 mergeBucketNodeMarks_.resetAll();
255 adjacentSeedMarks_.resetAll();
256 maxBucketSize_ = 0;
257 currentMergeLevelIndex_ = 0;
258 }
259
266 std::vector<NodeId>& getMergedNodes(const altitude_t& level) {
267 if constexpr (use_dense_levels) {
268 return mergeNodesByLevelStorage_[denseBucketIndex(level)];
269 } else {
270 return mergeNodesByLevelStorage_[level];
271 }
272 }
273
281 std::vector<NodeId>& getFrontierNodesAboveB() { return frontierNodesAboveB_; }
282
289 std::size_t getMaxBucketSize() const { return maxBucketSize_; }
290
298 bool markAdjacentSeed(NodeId nodeId) {
299 if (adjacentSeedMarks_.isMarked(static_cast<size_t>(nodeId))) {
300 return false;
301 }
302 adjacentSeedMarks_.mark(static_cast<size_t>(nodeId));
303 return true;
304 }
305
311 void addFrontierNodeAboveB(NodeId nodeId) {
312 if (!collectedNodeMarks_.isMarked(static_cast<size_t>(nodeId))) {
313 frontierNodesAboveB_.push_back(nodeId);
314 collectedNodeMarks_.mark(static_cast<size_t>(nodeId));
315 }
316 }
317
326 void addMergeNode(const tree_t& tree, NodeId nodeId) {
327 if (collectedNodeMarks_.isMarked(static_cast<size_t>(nodeId))) {
328 return;
329 }
330 auto& bucket = getMergedNodes(tree.nodeAltitude(nodeId));
331 bucket.push_back(nodeId);
332 maxBucketSize_ = std::max(maxBucketSize_, bucket.size());
333 collectedNodeMarks_.mark(static_cast<size_t>(nodeId));
334 mergeBucketNodeMarks_.mark(static_cast<size_t>(nodeId));
335 }
336
343 bool isMergeNode(NodeId nodeId) const { return mergeBucketNodeMarks_.isMarked(static_cast<size_t>(nodeId)); }
344
353 altitude_t firstMergeLevel() {
354 mergeLevels_.clear();
355 if constexpr (use_dense_levels) {
356 // Dense backend scans the discrete altitude domain and keeps
357 // only non-empty levels of the current step.
358 for (std::size_t i = 0; i < mergeNodesByLevelStorage_.size(); ++i) {
359 if (!mergeNodesByLevelStorage_[i].empty()) {
360 mergeLevels_.push_back(levelFromDenseBucketIndex(i));
361 }
362 }
363 } else {
364 // Sparse backend iterates only over levels that were instantiated.
365 mergeLevels_.reserve(mergeNodesByLevelStorage_.size());
366 for (const auto& entry : mergeNodesByLevelStorage_) {
367 if (!entry.second.empty()) {
368 mergeLevels_.push_back(entry.first);
369 }
370 }
371 }
372 if (mergeLevels_.empty()) {
373 return altitude_t{};
374 }
375 currentMergeLevelIndex_ = isMaxtree_ ? static_cast<int>(mergeLevels_.size()) - 1 : 0;
376 return mergeLevels_[static_cast<size_t>(currentMergeLevelIndex_)];
377 }
378
384 bool hasMergeLevel() const {
385 return !mergeLevels_.empty() && currentMergeLevelIndex_ >= 0 && currentMergeLevelIndex_ < static_cast<int>(mergeLevels_.size());
386 }
387
393 altitude_t nextMergeLevel() {
394 currentMergeLevelIndex_ = isMaxtree_ ? currentMergeLevelIndex_ - 1 : currentMergeLevelIndex_ + 1;
395 if (!hasMergeLevel()) {
396 return altitude_t{};
397 }
398 return mergeLevels_[static_cast<size_t>(currentMergeLevelIndex_)];
399 }
400 };
401
402 // Dynamic primal/dual trees and shared domain adjacency.
404 tree_t* mintree_ = nullptr;
406 tree_t* maxtree_ = nullptr;
408 const RegularGridAdjacency2D* graph_ = nullptr;
409
410 // Transient state of the current adjustment step.
412 MergedNodesCollection mergeNodesByLevel_;
414 GenerationStampSet removedMarks_;
416 GenerationStampSet pixelsInCMarks_;
418 GenerationStampSet climbedNodeMarks_;
420 GenerationStampSet attributeUpdateMarks_;
422 std::vector<PixelId> properPartSetC_;
424 std::vector<NodeId> nodesPendingRemoval_;
426 std::vector<NodeId> removedNodesPendingAbsorption_;
428 altitude_t altitudeCa_ = altitude_t{};
429
430 // Incremental attribute computation and external attribute buffers.
432 const attribute_computer_t* attributeComputerMin_ = nullptr;
434 const attribute_computer_t* attributeComputerMax_ = nullptr;
436 std::vector<double>* attributeBufferMin_ = nullptr;
438 std::vector<double>* attributeBufferMax_ = nullptr;
439
446 static const MorphologicalTree& topologyOf(const tree_t* tree) {
447 assert(tree != nullptr);
448 return tree->topology();
449 }
450
458 typename attribute_computer_t::buffer_type* getAttributeBuffer(tree_t* tree) const {
459 const auto* computer = tree == maxtree_ ? attributeComputerMax_ : attributeComputerMin_;
460 if (computer == nullptr) {
461 return nullptr;
462 }
463 return tree == maxtree_ ? attributeBufferMax_ : attributeBufferMin_;
464 }
465
472 const attribute_computer_t* getAttributeComputer(tree_t* tree) const {
473 if (tree == nullptr) {
474 return nullptr;
475 }
476 return tree == maxtree_ ? attributeComputerMax_ : attributeComputerMin_;
477 }
478
487 void notifyNodeRemoved(tree_t* tree, NodeId nodeId) const {
488 const auto* computer = getAttributeComputer(tree);
489 if (computer != nullptr && tree != nullptr && nodeId != InvalidNode) {
490 computer->onNodeRemoved(nodeId, *tree);
491 }
492 }
493
501 void notifyMoveProperParts(tree_t* tree, NodeId targetNodeId, NodeId sourceNodeId) const {
502 const auto* computer = getAttributeComputer(tree);
503 if (computer != nullptr && tree != nullptr) {
504 computer->onMoveProperParts(targetNodeId, sourceNodeId, *tree);
505 }
506 }
507
516 void notifyMoveProperPart(tree_t* tree, NodeId targetNodeId, NodeId sourceNodeId, PixelId pixelId) const {
517 const auto* computer = getAttributeComputer(tree);
518 if (computer != nullptr && tree != nullptr) {
519 computer->onMoveProperPart(targetNodeId, sourceNodeId, pixelId, *tree);
520 }
521 }
522
530 altitude_t nodeAltitude(const tree_t* tree, NodeId nodeId) const {
531 assert(tree != nullptr);
532 return tree->nodeAltitude(nodeId);
533 }
534
546 void computeAttributeOnTreeNode(tree_t* tree, NodeId nodeId) {
547 const auto* computer = getAttributeComputer(tree);
548 if (computer == nullptr || tree == nullptr || nodeId == InvalidNode || !topologyOf(tree).isNode(nodeId) || !topologyOf(tree).isAlive(nodeId)) {
549 return;
550 }
551
552 auto* buffer = getAttributeBuffer(tree);
553 assert(buffer != nullptr);
554 if (!attributeUpdateMarks_.isMarked(static_cast<size_t>(nodeId))) {
555 return;
556 }
557
558 const altitude_t level = nodeAltitude(tree, nodeId);
559 if ((tree == maxtree_ && level < altitudeCa_) || (tree == mintree_ && level > altitudeCa_)) {
560 attributeUpdateMarks_.unmark(static_cast<size_t>(nodeId));
561 return;
562 }
563
564 computer->computeAttributeOnNode(*tree, nodeId, *buffer);
565 attributeUpdateMarks_.unmark(static_cast<size_t>(nodeId));
566 }
567
571 void resetAttributeUpdateMarks() {
572 attributeUpdateMarks_.resetAll();
573 altitudeCa_ = altitude_t{};
574 }
575
582 void markAttributeUpdate(tree_t* tree, NodeId nodeId) {
583 if (getAttributeComputer(tree) == nullptr || tree == nullptr || nodeId == InvalidNode) {
584 return;
585 }
586 if (!topologyOf(tree).isNode(nodeId) || !topologyOf(tree).isAlive(nodeId)) {
587 return;
588 }
589 attributeUpdateMarks_.mark(static_cast<size_t>(nodeId));
590 }
591
602 void disconnect(tree_t* tree, editor_t& editor, NodeId nodeId, bool releaseNode) {
603 assert(tree != nullptr);
604 const MorphologicalTree& topology = topologyOf(tree);
605 if (topology.isRoot(nodeId)) {
606 return;
607 }
608 const NodeId parentId = topology.parent(nodeId);
609 if (parentId == InvalidNode || parentId == nodeId) {
610 return;
611 }
612 editor.removeChild(parentId, nodeId, releaseNode);
613 if (releaseNode) {
614 notifyNodeRemoved(tree, nodeId);
615 }
616 }
617
630 void moveSelectedProperPartsToNode(tree_t* dualTree, editor_t& editor, NodeId unionNode, const std::vector<PixelId>& properPartSetC) {
632 const NodeId smallestNodeId = topologyOf(dualTree).smallestNode(pixelId);
633 if (smallestNodeId == InvalidNode || smallestNodeId == unionNode) {
634 continue;
635 }
636
637 notifyMoveProperPart(dualTree, unionNode, smallestNodeId, pixelId);
638 editor.movePixelToProperPart(unionNode, smallestNodeId, pixelId);
639
640 if (topologyOf(dualTree).isAlive(smallestNodeId) && topologyOf(dualTree).properPartCardinality(smallestNodeId) == 0 && smallestNodeId != unionNode &&
641 !removedMarks_.isMarked(static_cast<size_t>(smallestNodeId))) {
642 removedMarks_.mark(static_cast<size_t>(smallestNodeId));
643 removedNodesPendingAbsorption_.push_back(smallestNodeId);
644 }
645 }
646 }
647
655 bool canAbsorbRemovedNode(tree_t* dualTree, NodeId nodeId) const {
656 return dualTree != nullptr && nodeId != InvalidNode && topologyOf(dualTree).isNode(nodeId) && topologyOf(dualTree).isAlive(nodeId) &&
657 removedMarks_.isMarked(static_cast<size_t>(nodeId)) && topologyOf(dualTree).properPartCardinality(nodeId) == 0;
658 }
659
668 NodeId absorbRemovedNonRootNode(tree_t* dualTree, editor_t& editor, NodeId removedNodeId) {
669 const MorphologicalTree& topology = topologyOf(dualTree);
670 const NodeId parentId = topology.parent(removedNodeId);
671 if (parentId == InvalidNode || parentId == removedNodeId || !topology.isAlive(parentId)) {
672 return InvalidNode;
673 }
674
675 const bool parentChanged = topology.getFirstChild(removedNodeId) != InvalidNode;
676 editor.moveChildren(parentId, removedNodeId);
677 notifyMoveProperParts(dualTree, parentId, removedNodeId);
678 editor.mergeProperParts(parentId, removedNodeId);
679 disconnect(dualTree, editor, removedNodeId, true);
680 if (parentChanged) {
681 markAttributeUpdate(dualTree, parentId);
682 }
683 computeAttributeOnTreeNode(dualTree, parentId);
684
685 if (topologyOf(dualTree).isAlive(parentId) && topologyOf(dualTree).properPartCardinality(parentId) == 0) {
686 removedMarks_.mark(static_cast<size_t>(parentId));
687 return parentId;
688 }
689 return InvalidNode;
690 }
691
699 void absorbRemovedRootNode(tree_t* dualTree, editor_t& editor, NodeId removedNodeId) {
700 const bool isMaxtree = dualTree == maxtree_;
701 const MorphologicalTree& topology = topologyOf(dualTree);
702 const NodeId firstChild = topology.getFirstChild(removedNodeId);
703 if (firstChild == InvalidNode) {
704 return;
705 }
706
707 NodeId newRoot = firstChild;
708 for (NodeId childId = firstChild; childId != InvalidNode; childId = topologyOf(dualTree).getNextSibling(childId)) {
709 if ((isMaxtree && nodeAltitude(dualTree, childId) < nodeAltitude(dualTree, newRoot)) ||
710 (!isMaxtree && nodeAltitude(dualTree, childId) > nodeAltitude(dualTree, newRoot))) {
712 }
713 }
714
715 bool rootChanged = false;
716 for (NodeId childId = firstChild; childId != InvalidNode;) {
717 const NodeId next = topologyOf(dualTree).getNextSibling(childId);
718 if (childId != newRoot && !topologyOf(dualTree).hasChild(newRoot, childId)) {
719 editor.detach(childId);
720 editor.attach(newRoot, childId);
721 rootChanged = true;
722 }
723 childId = next;
724 }
725
726 editor.setRoot(newRoot);
727 editor.releaseNode(removedNodeId);
728 notifyNodeRemoved(dualTree, removedNodeId);
729 if (rootChanged) {
730 markAttributeUpdate(dualTree, newRoot);
731 }
732 computeAttributeOnTreeNode(dualTree, newRoot);
733 }
734
742 void absorbRemovedNodes(tree_t* dualTree, editor_t& editor, const std::vector<NodeId>& removedNodeIds) {
743 struct Frame {
746 };
747
748 if (dualTree == nullptr || removedNodeIds.empty()) {
749 return;
750 }
751
752 std::vector<Frame> stack;
753 stack.reserve(std::max<std::size_t>(64, removedNodeIds.size()));
754 const auto makeFrame = [dualTree](NodeId nodeId) {
755 const MorphologicalTree& topology = topologyOf(dualTree);
756 if (nodeId == InvalidNode || !topology.isNode(nodeId) || !topology.isAlive(nodeId)) {
757 return Frame{nodeId, InvalidNode};
758 }
759 return Frame{nodeId, topology.getFirstChild(nodeId)};
760 };
761
762 for (auto it = removedNodeIds.rbegin(); it != removedNodeIds.rend(); ++it) {
763 if (*it != InvalidNode) {
764 stack.push_back(makeFrame(*it));
765 }
766 }
767
768 while (!stack.empty()) {
769 Frame& frame = stack.back();
771
772 if (!canAbsorbRemovedNode(dualTree, frame.nodeId)) {
773 stack.pop_back();
774 } else if (frame.nextChildId != InvalidNode) {
775 const NodeId childId = frame.nextChildId;
776 frame.nextChildId = topologyOf(dualTree).getNextSibling(childId);
777 stack.push_back(makeFrame(childId));
778 } else {
779 currentNodeId = frame.nodeId;
780 stack.pop_back();
781 }
782
783 if (canAbsorbRemovedNode(dualTree, currentNodeId)) {
784 if (topologyOf(dualTree).isRoot(currentNodeId)) {
785 absorbRemovedRootNode(dualTree, editor, currentNodeId);
786 } else {
787 const NodeId parentId = absorbRemovedNonRootNode(dualTree, editor, currentNodeId);
788 if (parentId != InvalidNode) {
789 stack.push_back(makeFrame(parentId));
790 }
791 }
792 }
793 }
794 }
795
809 NodeId collapseRemovedRootBranch(tree_t* dualTree, editor_t& editor, NodeId rootId, NodeId childId) {
810 assert(dualTree != nullptr);
811 const bool isMaxtree = dualTree == maxtree_;
812
814 while (current != InvalidNode && topologyOf(dualTree).isNode(current) && topologyOf(dualTree).isAlive(current) &&
815 topologyOf(dualTree).parent(current) == rootId && removedMarks_.isMarked(static_cast<size_t>(current)) &&
816 topologyOf(dualTree).properPartCardinality(current) == 0) {
817 const NodeId firstGrandchild = topologyOf(dualTree).getFirstChild(current);
819 break;
820 }
821
824 if ((isMaxtree && nodeAltitude(dualTree, grandchildId) < nodeAltitude(dualTree, promoted)) ||
825 (!isMaxtree && nodeAltitude(dualTree, grandchildId) > nodeAltitude(dualTree, promoted))) {
827 }
828 }
829
830 if (!topologyOf(dualTree).isRoot(promoted)) {
831 disconnect(dualTree, editor, promoted, false);
832 }
833 editor.attach(rootId, promoted);
834
835 for (NodeId grandchildId = topologyOf(dualTree).getFirstChild(current); grandchildId != InvalidNode;) {
836 const NodeId next = topologyOf(dualTree).getNextSibling(grandchildId);
837 if (grandchildId != promoted && !topologyOf(dualTree).hasChild(promoted, grandchildId)) {
838 if (!topologyOf(dualTree).isRoot(grandchildId)) {
839 disconnect(dualTree, editor, grandchildId, false);
840 }
842 }
843 grandchildId = next;
844 }
845
846 disconnect(dualTree, editor, current, true);
847 markAttributeUpdate(dualTree, promoted);
848 computeAttributeOnTreeNode(dualTree, promoted);
850 }
851 return current;
852 }
853
867 void finalizeUpdateTreeAndContractRemovedNodes(tree_t* dualTree, editor_t& editor, NodeId nodeCa, NodeId finalUnionNode) {
868 if (dualTree == nullptr) {
869 return;
870 }
871
872 const bool isMaxtree = dualTree == maxtree_;
873
874 if (finalUnionNode != InvalidNode && topologyOf(dualTree).isAlive(finalUnionNode)) {
875 if (removedMarks_.isMarked(static_cast<size_t>(nodeCa))) {
876 if (!topologyOf(dualTree).isRoot(nodeCa)) {
877 const NodeId nodeCaParentId = topologyOf(dualTree).parent(nodeCa);
878 bool finalUnionNodeChanged = false;
879
880 if (!topologyOf(dualTree).isRoot(finalUnionNode)) {
881 disconnect(dualTree, editor, finalUnionNode, false);
882 }
884
885 for (NodeId childId = topologyOf(dualTree).getFirstChild(nodeCa); childId != InvalidNode;) {
886 const NodeId next = topologyOf(dualTree).getNextSibling(childId);
887 if (childId != finalUnionNode && !topologyOf(dualTree).hasChild(finalUnionNode, childId)) {
888 if (!topologyOf(dualTree).isRoot(childId)) {
889 disconnect(dualTree, editor, childId, false);
890 }
893 }
894 childId = next;
895 }
896
897 disconnect(dualTree, editor, nodeCa, true);
899 markAttributeUpdate(dualTree, finalUnionNode);
900 }
901 markAttributeUpdate(dualTree, nodeCaParentId);
902 computeAttributeOnTreeNode(dualTree, finalUnionNode);
903 computeAttributeOnTreeNode(dualTree, nodeCaParentId);
904 } else {
906 for (NodeId childId = topologyOf(dualTree).getFirstChild(nodeCa); childId != InvalidNode;) {
907 const NodeId next = topologyOf(dualTree).getNextSibling(childId);
908 const NodeId normalizedChild = collapseRemovedRootBranch(dualTree, editor, nodeCa, childId);
911 }
912 childId = next;
913 }
914
916 for (NodeId childId = topologyOf(dualTree).getFirstChild(nodeCa); childId != InvalidNode;
917 childId = topologyOf(dualTree).getNextSibling(childId)) {
918 if ((isMaxtree && nodeAltitude(dualTree, childId) < nodeAltitude(dualTree, candidateRootId)) ||
919 (!isMaxtree && nodeAltitude(dualTree, childId) > nodeAltitude(dualTree, candidateRootId))) {
921 }
922 }
923
924 bool candidateRootChanged = false;
926 if (!topologyOf(dualTree).isRoot(survivingFinalUnionNode)) {
927 disconnect(dualTree, editor, survivingFinalUnionNode, false);
928 }
931 }
932
933 for (NodeId childId = topologyOf(dualTree).getFirstChild(nodeCa); childId != InvalidNode;) {
934 const NodeId next = topologyOf(dualTree).getNextSibling(childId);
935 if (childId != candidateRootId && !topologyOf(dualTree).hasChild(candidateRootId, childId)) {
936 if (!topologyOf(dualTree).isRoot(childId)) {
937 disconnect(dualTree, editor, childId, false);
938 }
941 }
942 childId = next;
943 }
944
945 editor.setRoot(candidateRootId);
946 editor.releaseNode(nodeCa);
947 notifyNodeRemoved(dualTree, nodeCa);
949 markAttributeUpdate(dualTree, candidateRootId);
950 }
951 computeAttributeOnTreeNode(dualTree, candidateRootId);
952 }
953 } else {
954 if (!topologyOf(dualTree).isRoot(finalUnionNode)) {
955 disconnect(dualTree, editor, finalUnionNode, false);
956 }
958 markAttributeUpdate(dualTree, nodeCa);
959 computeAttributeOnTreeNode(dualTree, nodeCa);
960 }
961 }
962
963 absorbRemovedNodes(dualTree, editor, removedNodesPendingAbsorption_);
964 }
965
974 tree_t* getPrimalTree(bool isMaxtree) { return isMaxtree ? mintree_ : maxtree_; }
975
988 void reattachOutsideIntervalChildren(tree_t* tree, editor_t& editor, NodeId targetNodeId, NodeId sourceNodeId) {
989 assert(tree != nullptr);
990 if (sourceNodeId == targetNodeId) {
991 for (NodeId childId = topologyOf(tree).getFirstChild(sourceNodeId); childId != InvalidNode;) {
992 const NodeId next = topologyOf(tree).getNextSibling(childId);
993 if (mergeNodesByLevel_.isMergeNode(childId)) {
994 editor.detach(childId);
995 }
996 childId = next;
997 }
998 return;
999 }
1000
1001 for (NodeId childId = topologyOf(tree).getFirstChild(sourceNodeId); childId != InvalidNode;) {
1002 const NodeId next = topologyOf(tree).getNextSibling(childId);
1003 if (mergeNodesByLevel_.isMergeNode(childId)) {
1004 editor.detach(childId);
1005 }
1006 childId = next;
1007 }
1008 editor.moveChildren(targetNodeId, sourceNodeId);
1009 }
1010
1030 void buildMergedAndNestedCollections(const tree_t& tree, const std::vector<PixelId>& properPartSetC, NodeId nodeCa, altitude_t b, bool isMaxtree) {
1031 mergeNodesByLevel_.resetCollection(isMaxtree);
1033 climbedNodeMarks_.resetAll();
1034 const altitude_t altitudeCa = nodeAltitude(&tree, nodeCa);
1035
1036 for (PixelId pixel : properPartSetC) {
1037 for (PixelId neighbor : graph_->getNeighborIndices(pixel)) {
1038 if (pixelsInCMarks_.isMarked(static_cast<size_t>(neighbor))) {
1039 continue;
1040 }
1041
1042 const NodeId nodeQ = tree.topology().smallestNode(neighbor);
1043 if (nodeQ == InvalidNode) {
1044 continue;
1045 }
1046
1047 const altitude_t altitudeQ = nodeAltitude(&tree, nodeQ);
1048 const bool validSeed = (isMaxtree && altitudeQ >= altitudeCa) || (!isMaxtree && altitudeQ <= altitudeCa);
1049 if (!validSeed) {
1050 continue;
1051 }
1052
1053 if (mergeNodesByLevel_.markAdjacentSeed(nodeQ)) {
1055 NodeId n = nodeQ;
1056 while (n != InvalidNode && tree.topology().isAlive(n) && !climbedNodeMarks_.isMarked(static_cast<size_t>(n))) {
1057 const altitude_t levelCurrent = nodeAltitude(&tree, n);
1059 break;
1060 }
1061
1062 climbedNodeMarks_.mark(static_cast<size_t>(n));
1063 nodeSubtree = n;
1064
1065 if ((isMaxtree && levelCurrent <= b) || (!isMaxtree && levelCurrent >= b)) {
1066 mergeNodesByLevel_.addMergeNode(tree, nodeSubtree);
1067 } else {
1068 NodeId parentId = tree.topology().parent(nodeSubtree);
1069 if (parentId == nodeSubtree) {
1071 }
1072 if (!(parentId != InvalidNode &&
1073 ((isMaxtree && nodeAltitude(&tree, parentId) > b) || (!isMaxtree && nodeAltitude(&tree, parentId) < b)))) {
1074 mergeNodesByLevel_.addFrontierNodeAboveB(nodeSubtree);
1075 }
1076 }
1077
1078 const NodeId parentId = tree.topology().parent(n);
1079 if (parentId == n) {
1080 break;
1081 }
1082 n = parentId;
1083 }
1084 }
1085 }
1086 }
1087 }
1088
1112 void updateTree(tree_t* dualTree, NodeId subtreeRoot) {
1113 assert(dualTree != nullptr);
1115 resetAttributeUpdateMarks();
1116
1117 const bool isMaxtree = dualTree == maxtree_;
1118 tree_t* primalTree = getPrimalTree(isMaxtree);
1119 assert(primalTree != nullptr);
1120 assert(primalTree->topology().isNode(subtreeRoot));
1121 assert(primalTree->topology().isAlive(subtreeRoot));
1122
1123 const NodeId subtreeParentId = primalTree->topology().parent(subtreeRoot);
1125 const altitude_t b = nodeAltitude(primalTree, subtreeParentId);
1126
1128 altitudeCa_ = altitude_t{};
1129
1130 properPartSetC_.clear();
1131 properPartSetC_.reserve(64);
1132 pixelsInCMarks_.resetAll();
1133 for (NodeId subtreeNodeId : primalTree->topology().subtreeNodes(subtreeRoot)) {
1134 for (PixelId pixel : primalTree->topology().properPart(subtreeNodeId)) {
1135 properPartSetC_.push_back(pixel);
1136 pixelsInCMarks_.mark(static_cast<size_t>(pixel));
1137
1138 const NodeId nodeP = dualTree->topology().smallestNode(pixel);
1139 if (nodeP == InvalidNode) {
1140 continue;
1141 }
1142 const altitude_t altitudeP = nodeAltitude(dualTree, nodeP);
1143 if (nodeCa == InvalidNode || ((isMaxtree && altitudeP < altitudeCa_) || (!isMaxtree && altitudeP > altitudeCa_))) {
1144 altitudeCa_ = altitudeP;
1145 nodeCa = nodeP;
1146 }
1147 }
1148 }
1149 if (properPartSetC_.empty()) {
1150 return;
1151 }
1153
1154 buildMergedAndNestedCollections(*dualTree, properPartSetC_, nodeCa, b, isMaxtree);
1155
1156 editor_t editor = mmcfilters::detail::beginEstablishedValuedEdit(*dualTree);
1157 altitude_t currentMergeLevel = mergeNodesByLevel_.firstMergeLevel();
1160 removedMarks_.resetAll();
1161 removedNodesPendingAbsorption_.clear();
1162 nodesPendingRemoval_.reserve(mergeNodesByLevel_.getMaxBucketSize());
1163
1164 while (mergeNodesByLevel_.hasMergeLevel() && ((isMaxtree && currentMergeLevel > altitudeCa_) || (!isMaxtree && currentMergeLevel < altitudeCa_))) {
1165 auto& nodesAtCurrentLevel = mergeNodesByLevel_.getMergedNodes(currentMergeLevel);
1167 nodesPendingRemoval_.clear();
1168
1170 if (!dualTree->topology().isAlive(nodeId)) {
1171 continue;
1172 }
1173
1175 if (removedMarks_.isMarked(static_cast<size_t>(nodeId))) {
1176 nodesPendingRemoval_.push_back(nodeId);
1177 continue;
1178 }
1180 disconnect(dualTree, editor, currentUnionNode, false);
1181 reattachOutsideIntervalChildren(dualTree, editor, currentUnionNode, currentUnionNode);
1182
1183 for (NodeId pendingNodeId : nodesPendingRemoval_) {
1184 reattachOutsideIntervalChildren(dualTree, editor, currentUnionNode, pendingNodeId);
1185 notifyMoveProperParts(dualTree, currentUnionNode, pendingNodeId);
1186 editor.mergeProperParts(currentUnionNode, pendingNodeId);
1187 disconnect(dualTree, editor, pendingNodeId, true);
1188 }
1189 nodesPendingRemoval_.clear();
1190 continue;
1191 }
1192
1193 reattachOutsideIntervalChildren(dualTree, editor, currentUnionNode, nodeId);
1194 notifyMoveProperParts(dualTree, currentUnionNode, nodeId);
1195 editor.mergeProperParts(currentUnionNode, nodeId);
1196 disconnect(dualTree, editor, nodeId, true);
1197 }
1198
1200 for (NodeId nodeId : nodesPendingRemoval_) {
1201 if (!dualTree->topology().isAlive(nodeId) || dualTree->topology().isRoot(nodeId)) {
1202 continue;
1203 }
1204 const NodeId parentId = dualTree->topology().parent(nodeId);
1205 editor.moveChildren(parentId, nodeId);
1206 notifyMoveProperParts(dualTree, parentId, nodeId);
1207 editor.mergeProperParts(parentId, nodeId);
1208 disconnect(dualTree, editor, nodeId, true);
1209 }
1210 currentMergeLevel = mergeNodesByLevel_.nextMergeLevel();
1212 continue;
1213 }
1214
1215 if (currentMergeLevel == b) {
1216 moveSelectedProperPartsToNode(dualTree, editor, currentUnionNode, properPartSetC_);
1217 for (NodeId nodeId : mergeNodesByLevel_.getFrontierNodesAboveB()) {
1218 disconnect(dualTree, editor, nodeId, false);
1220 }
1221 }
1222
1224 if (dualTree->topology().isAlive(previousLevelUnionNode) && !dualTree->topology().hasChild(currentUnionNode, previousLevelUnionNode)) {
1225 if (!dualTree->topology().isRoot(previousLevelUnionNode)) {
1226 disconnect(dualTree, editor, previousLevelUnionNode, false);
1227 }
1229 }
1230 }
1231
1232 markAttributeUpdate(dualTree, currentUnionNode);
1233 computeAttributeOnTreeNode(dualTree, currentUnionNode);
1234
1236 currentMergeLevel = mergeNodesByLevel_.nextMergeLevel();
1237 }
1238
1239 finalizeUpdateTreeAndContractRemovedNodes(dualTree, editor, nodeCa, previousLevelUnionNode);
1240 auto proof = editor.proveIncremental();
1241 editor.commit(std::move(proof));
1242 }
1243
1244 public:
1250 static constexpr bool usesDenseLevelBackend() { return use_dense_levels; }
1251
1257 static constexpr int denseLevelBackendMaxBits() { return MMCFILTERS_COMPONENT_TREE_ADJUSTMENT_DENSE_MAX_BITS; }
1258
1271 : mintree_(mintree), maxtree_(maxtree), graph_(&graph), mergeNodesByLevel_(std::max(mintree ? mintree->topology().numInternalNodeSlots() : 0,
1272 maxtree ? maxtree->topology().numInternalNodeSlots() : 0)),
1273 removedMarks_(static_cast<size_t>(
1274 std::max(mintree ? mintree->topology().numInternalNodeSlots() : 0, maxtree ? maxtree->topology().numInternalNodeSlots() : 0))),
1275 pixelsInCMarks_(static_cast<size_t>(
1276 std::max(mintree ? mintree->topology().numPixels() : 0, maxtree ? maxtree->topology().numPixels() : 0))),
1277 climbedNodeMarks_(static_cast<size_t>(
1278 std::max(mintree ? mintree->topology().numInternalNodeSlots() : 0, maxtree ? maxtree->topology().numInternalNodeSlots() : 0))),
1279 attributeUpdateMarks_(static_cast<size_t>(
1280 std::max(mintree ? mintree->topology().numInternalNodeSlots() : 0, maxtree ? maxtree->topology().numInternalNodeSlots() : 0))) {
1281 assert(mintree_ != nullptr);
1282 assert(maxtree_ != nullptr);
1283 assert(graph_ != nullptr);
1284 }
1285
1298 std::vector<double>& bufferMax) {
1299 attributeComputerMin_ = &computerMin;
1300 attributeComputerMax_ = &computerMax;
1301 attributeBufferMin_ = &bufferMin;
1302 attributeBufferMax_ = &bufferMax;
1303 }
1304
1312 void setAttributeComputer(const attribute_computer_t& computer, std::vector<double>& bufferMin, std::vector<double>& bufferMax) {
1314 }
1315
1325 void pruneMaxTreeAndUpdateMinTree(const std::vector<NodeId>& nodesToPrune) {
1326 assert(mintree_ != nullptr);
1327 assert(maxtree_ != nullptr);
1329 if (rootSubtree == InvalidNode || rootSubtree == maxtree_->topology().root() || !maxtree_->topology().isNode(rootSubtree) ||
1330 !maxtree_->topology().isAlive(rootSubtree)) {
1331 continue;
1332 }
1333 updateTree(mintree_, rootSubtree);
1334 maxtree_->pruneNode(rootSubtree);
1335 }
1336 }
1337
1347 void pruneMinTreeAndUpdateMaxTree(const std::vector<NodeId>& nodesToPrune) {
1348 assert(mintree_ != nullptr);
1349 assert(maxtree_ != nullptr);
1351 if (rootSubtree == InvalidNode || rootSubtree == mintree_->topology().root() || !mintree_->topology().isNode(rootSubtree) ||
1352 !mintree_->topology().isAlive(rootSubtree)) {
1353 continue;
1354 }
1355 updateTree(maxtree_, rootSubtree);
1356 mintree_->pruneNode(rootSubtree);
1357 }
1358 }
1359};
1360
1361} // namespace mmcfilters::adjust
int PixelId
Pixel identifier type used by source and active construction domains.
Definition Common.hpp:26
int NodeId
Node identifier type used throughout the project.
Definition Common.hpp:17
constexpr NodeId InvalidNode
Sentinel value used to denote an invalid node identifier.
Definition Common.hpp:34
Mutable connected-subset tree on a finite pixel domain.
bool isNode(NodeId nodeId) const noexcept
Tests whether nodeId belongs to the internal-node id domain.
bool isAlive(NodeId nodeId) const
Tests whether a node slot currently represents a live node.
bool isRoot(NodeId nodeId) const
Tests whether nodeId is the current root.
NodeId getFirstChild(NodeId nodeId) const
Returns the first direct child of nodeId, or InvalidNode.
NodeId parent(NodeId nodeId) const
Returns the direct parent of nodeId.
Immutable regular-grid 2D adjacency with allocation-free traversal.
NeighborIndexRange getNeighborIndices(int row, int column) const
Returns valid neighbouring grid indices excluding the origin.
Incremental updater for a paired min-tree / max-tree state.
void setAttributeComputer(const attribute_computer_t &computer, std::vector< double > &bufferMin, std::vector< double > &bufferMax)
Registers one shared incremental attribute computer for both trees.
void setAttributeComputer(const attribute_computer_t &computerMin, const attribute_computer_t &computerMax, std::vector< double > &bufferMin, std::vector< double > &bufferMax)
Registers incremental attribute computers and their external buffers.
void pruneMinTreeAndUpdateMaxTree(const std::vector< NodeId > &nodesToPrune)
Prunes min-tree subtrees and updates the max-tree before each prune.
static constexpr int denseLevelBackendMaxBits()
Returns the bit-width threshold for dense bucket selection.
static constexpr bool usesDenseLevelBackend()
Reports whether this altitude type uses dense per-level buckets.
DualMinMaxTreeIncrementalFilter(tree_t *mintree, tree_t *maxtree, const RegularGridAdjacency2D &graph)
Creates an adjuster for externally owned min/max-tree states.
void pruneMaxTreeAndUpdateMinTree(const std::vector< NodeId > &nodesToPrune)
Prunes max-tree subtrees and updates the min-tree before each prune.
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.
Efficient visited-set implementation based on generation stamps.
void mark(size_t idx) noexcept
Marks idx in the current generation.
bool isMarked(size_t idx) const noexcept
Returns true when idx is marked in the current generation.
void resetAll()
Performs an O(1) logical reset by advancing the generation counter.
void unmark(size_t idx) noexcept
Removes one mark from the current logical generation.