MorphologicalAttributeFilters
Public API documentation
Loading...
Searching...
No Matches
DualMinMaxTreeIncrementalFilter.hpp
1#pragma once
2
3#include "DynamicTreeAttributeComputer.hpp"
4#include "../WeightedMorphologicalTree.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 <vector>
16
17#ifndef MMCFILTERS_COMPONENT_TREE_ADJUSTMENT_DENSE_MAX_BITS
18#define MMCFILTERS_COMPONENT_TREE_ADJUSTMENT_DENSE_MAX_BITS 8
19#endif
20
21namespace mmcfilters::adjust {
22
104template<AltitudeValue altitude_t>
106private:
110
117 static constexpr bool usesDenseLevels() {
118 if constexpr (std::is_integral_v<altitude_t>) {
119 using unsigned_altitude_t = std::make_unsigned_t<altitude_t>;
120 return std::numeric_limits<unsigned_altitude_t>::digits <= MMCFILTERS_COMPONENT_TREE_ADJUSTMENT_DENSE_MAX_BITS;
121 } else {
122 return false;
123 }
124 }
125
129 static constexpr bool use_dense_levels = usesDenseLevels();
130
140 class MergedNodesCollection {
141 private:
142 template<bool dense, typename altitude_type>
143 struct StorageSelector;
144
148 template<typename altitude_type>
149 struct StorageSelector<true, altitude_type> {
150 static constexpr std::size_t domain_size =
151 static_cast<std::size_t>(
152 static_cast<long long>(std::numeric_limits<altitude_type>::max()) -
153 static_cast<long long>(std::numeric_limits<altitude_type>::lowest()) + 1);
154 using type = std::array<std::vector<NodeId>, domain_size>;
155 };
156
160 template<typename altitude_type>
161 struct StorageSelector<false, altitude_type> {
162 using type = std::map<altitude_type, std::vector<NodeId>>;
163 };
164
165 using storage_t = typename StorageSelector<use_dense_levels, altitude_t>::type;
166
167 storage_t mergeNodesByLevelStorage_;
168 std::vector<altitude_t> mergeLevels_;
169 std::vector<NodeId> frontierNodesAboveB_;
170 GenerationStampSet collectedNodeMarks_;
171 GenerationStampSet mergeBucketNodeMarks_;
172 GenerationStampSet adjacentSeedMarks_;
173 std::size_t maxBucketSize_ = 0;
174 int currentMergeLevelIndex_ = 0;
175 bool isMaxtree_ = false;
176
177 public:
183 static std::size_t denseBucketIndex(altitude_t level) {
184 if constexpr (std::is_signed_v<altitude_t>) {
185 return static_cast<std::size_t>(
186 static_cast<long long>(level) -
187 static_cast<long long>(std::numeric_limits<altitude_t>::lowest()));
188 } else {
189 return static_cast<std::size_t>(level);
190 }
191 }
192
196 static altitude_t levelFromDenseBucketIndex(std::size_t index) {
197 if constexpr (std::is_signed_v<altitude_t>) {
198 return static_cast<altitude_t>(
199 static_cast<long long>(index) +
200 static_cast<long long>(std::numeric_limits<altitude_t>::lowest()));
201 } else {
202 return static_cast<altitude_t>(index);
203 }
204 }
205
210 explicit MergedNodesCollection(int maxNodes = 0)
211 : collectedNodeMarks_(static_cast<size_t>(std::max(maxNodes, 0))),
212 mergeBucketNodeMarks_(static_cast<size_t>(std::max(maxNodes, 0))),
213 adjacentSeedMarks_(static_cast<size_t>(std::max(maxNodes, 0))) {}
214
219 void resetCollection(bool isMaxtree) {
220 isMaxtree_ = isMaxtree;
221 for (altitude_t level : mergeLevels_) {
222 if constexpr (use_dense_levels) {
223 // Dense backend reuses one bucket per altitude value and
224 // clears only levels that were active in the previous step.
225 mergeNodesByLevelStorage_[denseBucketIndex(level)].clear();
226 } else {
227 // Sparse backend stores only touched levels, so only those
228 // buckets need to be cleared here.
229 auto it = mergeNodesByLevelStorage_.find(level);
230 if (it != mergeNodesByLevelStorage_.end()) {
231 it->second.clear();
232 }
233 }
234 }
235 mergeLevels_.clear();
236 frontierNodesAboveB_.clear();
237 collectedNodeMarks_.resetAll();
238 mergeBucketNodeMarks_.resetAll();
239 adjacentSeedMarks_.resetAll();
240 maxBucketSize_ = 0;
241 currentMergeLevelIndex_ = 0;
242 }
243
247 std::vector<NodeId>& getMergedNodes(const altitude_t& level) {
248 if constexpr (use_dense_levels) {
249 return mergeNodesByLevelStorage_[denseBucketIndex(level)];
250 } else {
251 return mergeNodesByLevelStorage_[level];
252 }
253 }
254
260 std::vector<NodeId>& getFrontierNodesAboveB() { return frontierNodesAboveB_; }
261
266 std::size_t getMaxBucketSize() const { return maxBucketSize_; }
267
272 bool markAdjacentSeed(NodeId nodeId) {
273 if (adjacentSeedMarks_.isMarked(static_cast<size_t>(nodeId))) {
274 return false;
275 }
276 adjacentSeedMarks_.mark(static_cast<size_t>(nodeId));
277 return true;
278 }
279
283 void addFrontierNodeAboveB(NodeId nodeId) {
284 if (!collectedNodeMarks_.isMarked(static_cast<size_t>(nodeId))) {
285 frontierNodesAboveB_.push_back(nodeId);
286 collectedNodeMarks_.mark(static_cast<size_t>(nodeId));
287 }
288 }
289
295 void addMergeNode(const tree_t& tree, NodeId nodeId) {
296 if (collectedNodeMarks_.isMarked(static_cast<size_t>(nodeId))) {
297 return;
298 }
299 auto& bucket = getMergedNodes(tree.getAltitude(nodeId));
300 bucket.push_back(nodeId);
301 maxBucketSize_ = std::max(maxBucketSize_, bucket.size());
302 collectedNodeMarks_.mark(static_cast<size_t>(nodeId));
303 mergeBucketNodeMarks_.mark(static_cast<size_t>(nodeId));
304 }
305
309 bool isMergeNode(NodeId nodeId) const {
310 return mergeBucketNodeMarks_.isMarked(static_cast<size_t>(nodeId));
311 }
312
319 altitude_t firstMergeLevel() {
320 mergeLevels_.clear();
321 if constexpr (use_dense_levels) {
322 // Dense backend scans the discrete altitude domain and keeps
323 // only non-empty levels of the current step.
324 for (std::size_t i = 0; i < mergeNodesByLevelStorage_.size(); ++i) {
325 if (!mergeNodesByLevelStorage_[i].empty()) {
326 mergeLevels_.push_back(levelFromDenseBucketIndex(i));
327 }
328 }
329 } else {
330 // Sparse backend iterates only over levels that were instantiated.
331 mergeLevels_.reserve(mergeNodesByLevelStorage_.size());
332 for (const auto& entry : mergeNodesByLevelStorage_) {
333 if (!entry.second.empty()) {
334 mergeLevels_.push_back(entry.first);
335 }
336 }
337 }
338 if (mergeLevels_.empty()) {
339 return altitude_t{};
340 }
341 currentMergeLevelIndex_ = isMaxtree_ ? static_cast<int>(mergeLevels_.size()) - 1 : 0;
342 return mergeLevels_[static_cast<size_t>(currentMergeLevelIndex_)];
343 }
344
348 bool hasMergeLevel() const {
349 return !mergeLevels_.empty() &&
350 currentMergeLevelIndex_ >= 0 &&
352 }
353
357 altitude_t nextMergeLevel() {
358 currentMergeLevelIndex_ = isMaxtree_ ? currentMergeLevelIndex_ - 1 : currentMergeLevelIndex_ + 1;
359 if (!hasMergeLevel()) {
360 return altitude_t{};
361 }
362 return mergeLevels_[static_cast<size_t>(currentMergeLevelIndex_)];
363 }
364 };
365
366 // Dynamic primal/dual trees and shared domain adjacency.
367 tree_t* mintree_ = nullptr;
368 tree_t* maxtree_ = nullptr;
369 AdjacencyRelation* graph_ = nullptr;
370
371 // Transient state of the current adjustment step.
372 MergedNodesCollection mergeNodesByLevel_;
373 GenerationStampSet removedMarks_;
374 GenerationStampSet pixelsInCMarks_;
375 GenerationStampSet climbedNodeMarks_;
376 GenerationStampSet attributeUpdateMarks_;
377 std::vector<NodeId> properPartSetC_;
378 std::vector<NodeId> nodesPendingRemoval_;
379 std::vector<NodeId> removedNodesPendingAbsorption_;
380 altitude_t altitudeCa_ = altitude_t{};
381
382 // Incremental attribute computation and external attribute buffers.
383 const attribute_computer_t* attributeComputerMin_ = nullptr;
384 const attribute_computer_t* attributeComputerMax_ = nullptr;
385 std::vector<double>* attributeBufferMin_ = nullptr;
386 std::vector<double>* attributeBufferMax_ = nullptr;
387
391 static const MorphologicalTree& topologyOf(const tree_t* tree) {
392 assert(tree != nullptr);
393 return tree->topology();
394 }
395
400 typename attribute_computer_t::buffer_type* getAttributeBuffer(tree_t* tree) const {
401 const auto* computer = tree == maxtree_ ? attributeComputerMax_ : attributeComputerMin_;
402 if (computer == nullptr) {
403 return nullptr;
404 }
405 return tree == maxtree_ ? attributeBufferMax_ : attributeBufferMin_;
406 }
407
411 const attribute_computer_t* getAttributeComputer(tree_t* tree) const {
412 if (tree == nullptr) {
413 return nullptr;
414 }
415 return tree == maxtree_ ? attributeComputerMax_ : attributeComputerMin_;
416 }
417
423 void notifyNodeRemoved(tree_t* tree, NodeId nodeId) const {
424 const auto* computer = getAttributeComputer(tree);
425 if (computer != nullptr && tree != nullptr && nodeId != InvalidNode) {
426 computer->onNodeRemoved(nodeId, *tree);
427 }
428 }
429
433 void notifyMoveProperParts(tree_t* tree, NodeId targetNodeId, NodeId sourceNodeId) const {
434 const auto* computer = getAttributeComputer(tree);
435 if (computer != nullptr && tree != nullptr) {
436 computer->onMoveProperParts(targetNodeId, sourceNodeId, *tree);
437 }
438 }
439
443 void notifyMoveProperPart(tree_t* tree, NodeId targetNodeId, NodeId sourceNodeId, NodeId pixelId) const {
444 const auto* computer = getAttributeComputer(tree);
445 if (computer != nullptr && tree != nullptr) {
446 computer->onMoveProperPart(targetNodeId, sourceNodeId, pixelId, *tree);
447 }
448 }
449
453 altitude_t nodeAltitude(const tree_t* tree, NodeId nodeId) const {
454 assert(tree != nullptr);
455 return tree->getAltitude(nodeId);
456 }
457
466 void computeAttributeOnTreeNode(tree_t* tree, NodeId nodeId) {
467 const auto* computer = getAttributeComputer(tree);
468 if (computer == nullptr || tree == nullptr || nodeId == InvalidNode ||
469 !topologyOf(tree).isNode(nodeId) || !topologyOf(tree).isAlive(nodeId)) {
470 return;
471 }
472
473 auto* buffer = getAttributeBuffer(tree);
474 assert(buffer != nullptr);
475 if (!attributeUpdateMarks_.isMarked(static_cast<size_t>(nodeId))) {
476 return;
477 }
478
479 const altitude_t level = nodeAltitude(tree, nodeId);
480 if ((tree == maxtree_ && level < altitudeCa_) || (tree == mintree_ && level > altitudeCa_)) {
481 attributeUpdateMarks_.unmark(static_cast<size_t>(nodeId));
482 return;
483 }
484
485 computer->computeAttributeOnNode(*tree, nodeId, *buffer);
486 attributeUpdateMarks_.unmark(static_cast<size_t>(nodeId));
487 }
488
492 void resetAttributeUpdateMarks() {
493 attributeUpdateMarks_.resetAll();
494 altitudeCa_ = altitude_t{};
495 }
496
500 void markAttributeUpdate(tree_t* tree, NodeId nodeId) {
501 if (getAttributeComputer(tree) == nullptr || tree == nullptr || nodeId == InvalidNode) {
502 return;
503 }
504 if (!topologyOf(tree).isNode(nodeId) || !topologyOf(tree).isAlive(nodeId)) {
505 return;
506 }
507 attributeUpdateMarks_.mark(static_cast<size_t>(nodeId));
508 }
509
515 void disconnect(tree_t* tree, editor_t& editor, NodeId nodeId, bool releaseNode) {
516 assert(tree != nullptr);
517 const MorphologicalTree& topology = topologyOf(tree);
518 if (topology.isRoot(nodeId)) {
519 return;
520 }
521 const NodeId parentId = topology.getNodeParent(nodeId);
522 if (parentId == InvalidNode || parentId == nodeId) {
523 return;
524 }
525 editor.removeChild(parentId, nodeId, releaseNode);
526 if (releaseNode) {
527 notifyNodeRemoved(tree, nodeId);
528 }
529 }
530
538 void moveSelectedProperPartsToNode(tree_t* dualTree, editor_t& editor, NodeId unionNode, const std::vector<NodeId>& properPartSetC) {
540 const NodeId ownerId = topologyOf(dualTree).getProperPartOwner(pixelId);
541 if (ownerId == InvalidNode || ownerId == unionNode) {
542 continue;
543 }
544
545 notifyMoveProperPart(dualTree, unionNode, ownerId, pixelId);
546 editor.moveProperPart(unionNode, ownerId, pixelId);
547
548 if (topologyOf(dualTree).isAlive(ownerId) &&
549 topologyOf(dualTree).getNumProperParts(ownerId) == 0 &&
550 ownerId != unionNode &&
551 !removedMarks_.isMarked(static_cast<size_t>(ownerId))) {
552 removedMarks_.mark(static_cast<size_t>(ownerId));
553 removedNodesPendingAbsorption_.push_back(ownerId);
554 }
555 }
556 }
557
561 bool canAbsorbRemovedNode(tree_t* dualTree, NodeId nodeId) const {
562 return dualTree != nullptr &&
563 nodeId != InvalidNode &&
564 topologyOf(dualTree).isNode(nodeId) &&
565 topologyOf(dualTree).isAlive(nodeId) &&
566 removedMarks_.isMarked(static_cast<size_t>(nodeId)) &&
567 topologyOf(dualTree).getNumProperParts(nodeId) == 0;
568 }
569
573 NodeId absorbRemovedNonRootNode(tree_t* dualTree, editor_t& editor, NodeId removedNodeId) {
574 const MorphologicalTree& topology = topologyOf(dualTree);
575 const NodeId parentId = topology.getNodeParent(removedNodeId);
576 if (parentId == InvalidNode || parentId == removedNodeId || !topology.isAlive(parentId)) {
577 return InvalidNode;
578 }
579
580 const bool parentChanged = topology.getFirstChild(removedNodeId) != InvalidNode;
581 editor.moveChildren(parentId, removedNodeId);
582 notifyMoveProperParts(dualTree, parentId, removedNodeId);
583 editor.moveProperParts(parentId, removedNodeId);
584 disconnect(dualTree, editor, removedNodeId, true);
585 if (parentChanged) {
586 markAttributeUpdate(dualTree, parentId);
587 }
588 computeAttributeOnTreeNode(dualTree, parentId);
589
590 if (topologyOf(dualTree).isAlive(parentId) && topologyOf(dualTree).getNumProperParts(parentId) == 0) {
591 removedMarks_.mark(static_cast<size_t>(parentId));
592 return parentId;
593 }
594 return InvalidNode;
595 }
596
600 void absorbRemovedRootNode(tree_t* dualTree, editor_t& editor, NodeId removedNodeId) {
601 const bool isMaxtree = dualTree == maxtree_;
602 const MorphologicalTree& topology = topologyOf(dualTree);
604 if (firstChild == InvalidNode) {
605 return;
606 }
607
609 for (NodeId childId = firstChild; childId != InvalidNode; childId = topologyOf(dualTree).getNextSibling(childId)) {
610 if ((isMaxtree && nodeAltitude(dualTree, childId) < nodeAltitude(dualTree, newRoot)) ||
611 (!isMaxtree && nodeAltitude(dualTree, childId) > nodeAltitude(dualTree, newRoot))) {
613 }
614 }
615
616 bool rootChanged = false;
618 const NodeId next = topologyOf(dualTree).getNextSibling(childId);
619 if (childId != newRoot && !topologyOf(dualTree).hasChild(newRoot, childId)) {
620 editor.detach(childId);
621 editor.attach(newRoot, childId);
622 rootChanged = true;
623 }
624 childId = next;
625 }
626
627 editor.setRoot(newRoot);
628 editor.releaseNode(removedNodeId);
629 notifyNodeRemoved(dualTree, removedNodeId);
630 if (rootChanged) {
631 markAttributeUpdate(dualTree, newRoot);
632 }
633 computeAttributeOnTreeNode(dualTree, newRoot);
634 }
635
639 void absorbRemovedNodes(tree_t* dualTree, editor_t& editor, const std::vector<NodeId>& removedNodeIds) {
640 struct Frame {
643 };
644
645 if (dualTree == nullptr || removedNodeIds.empty()) {
646 return;
647 }
648
649 std::vector<Frame> stack;
650 stack.reserve(std::max<std::size_t>(64, removedNodeIds.size()));
651 const auto makeFrame = [dualTree](NodeId nodeId) {
652 const MorphologicalTree& topology = topologyOf(dualTree);
653 if (nodeId == InvalidNode || !topology.isNode(nodeId) || !topology.isAlive(nodeId)) {
654 return Frame{nodeId, InvalidNode};
655 }
656 return Frame{nodeId, topology.getFirstChild(nodeId)};
657 };
658
659 for (auto it = removedNodeIds.rbegin(); it != removedNodeIds.rend(); ++it) {
660 if (*it != InvalidNode) {
661 stack.push_back(makeFrame(*it));
662 }
663 }
664
665 while (!stack.empty()) {
666 Frame& frame = stack.back();
668
669 if (!canAbsorbRemovedNode(dualTree, frame.nodeId)) {
670 stack.pop_back();
671 } else if (frame.nextChildId != InvalidNode) {
672 const NodeId childId = frame.nextChildId;
673 frame.nextChildId = topologyOf(dualTree).getNextSibling(childId);
674 stack.push_back(makeFrame(childId));
675 } else {
676 currentNodeId = frame.nodeId;
677 stack.pop_back();
678 }
679
680 if (canAbsorbRemovedNode(dualTree, currentNodeId)) {
681 if (topologyOf(dualTree).isRoot(currentNodeId)) {
682 absorbRemovedRootNode(dualTree, editor, currentNodeId);
683 } else {
684 const NodeId parentId = absorbRemovedNonRootNode(dualTree, editor, currentNodeId);
685 if (parentId != InvalidNode) {
686 stack.push_back(makeFrame(parentId));
687 }
688 }
689 }
690 }
691 }
692
700 NodeId collapseRemovedRootBranch(tree_t* dualTree, editor_t& editor, NodeId rootId, NodeId childId) {
701 assert(dualTree != nullptr);
702 const bool isMaxtree = dualTree == maxtree_;
703
705 while (current != InvalidNode &&
706 topologyOf(dualTree).isNode(current) &&
707 topologyOf(dualTree).isAlive(current) &&
708 topologyOf(dualTree).getNodeParent(current) == rootId &&
709 removedMarks_.isMarked(static_cast<size_t>(current)) &&
710 topologyOf(dualTree).getNumProperParts(current) == 0) {
711 const NodeId firstGrandchild = topologyOf(dualTree).getFirstChild(current);
713 break;
714 }
715
718 if ((isMaxtree && nodeAltitude(dualTree, grandchildId) < nodeAltitude(dualTree, promoted)) ||
719 (!isMaxtree && nodeAltitude(dualTree, grandchildId) > nodeAltitude(dualTree, promoted))) {
721 }
722 }
723
724 if (!topologyOf(dualTree).isRoot(promoted)) {
725 disconnect(dualTree, editor, promoted, false);
726 }
727 editor.attach(rootId, promoted);
728
729 for (NodeId grandchildId = topologyOf(dualTree).getFirstChild(current); grandchildId != InvalidNode;) {
730 const NodeId next = topologyOf(dualTree).getNextSibling(grandchildId);
731 if (grandchildId != promoted && !topologyOf(dualTree).hasChild(promoted, grandchildId)) {
732 if (!topologyOf(dualTree).isRoot(grandchildId)) {
733 disconnect(dualTree, editor, grandchildId, false);
734 }
736 }
738 }
739
740 disconnect(dualTree, editor, current, true);
741 markAttributeUpdate(dualTree, promoted);
742 computeAttributeOnTreeNode(dualTree, promoted);
744 }
745 return current;
746 }
747
756 void finalizeUpdateTreeAndContractRemovedNodes(tree_t* dualTree, editor_t& editor, NodeId nodeCa, NodeId finalUnionNode) {
757 if (dualTree == nullptr) {
758 return;
759 }
760
761 const bool isMaxtree = dualTree == maxtree_;
762
763 if (finalUnionNode != InvalidNode && topologyOf(dualTree).isAlive(finalUnionNode)) {
764 if (removedMarks_.isMarked(static_cast<size_t>(nodeCa))) {
765 if (!topologyOf(dualTree).isRoot(nodeCa)) {
766 const NodeId nodeCaParentId = topologyOf(dualTree).getNodeParent(nodeCa);
767 bool finalUnionNodeChanged = false;
768
769 if (!topologyOf(dualTree).isRoot(finalUnionNode)) {
770 disconnect(dualTree, editor, finalUnionNode, false);
771 }
773
774 for (NodeId childId = topologyOf(dualTree).getFirstChild(nodeCa); childId != InvalidNode;) {
775 const NodeId next = topologyOf(dualTree).getNextSibling(childId);
776 if (childId != finalUnionNode && !topologyOf(dualTree).hasChild(finalUnionNode, childId)) {
777 if (!topologyOf(dualTree).isRoot(childId)) {
778 disconnect(dualTree, editor, childId, false);
779 }
782 }
783 childId = next;
784 }
785
786 disconnect(dualTree, editor, nodeCa, true);
788 markAttributeUpdate(dualTree, finalUnionNode);
789 }
790 markAttributeUpdate(dualTree, nodeCaParentId);
791 computeAttributeOnTreeNode(dualTree, finalUnionNode);
792 computeAttributeOnTreeNode(dualTree, nodeCaParentId);
793 } else {
795 for (NodeId childId = topologyOf(dualTree).getFirstChild(nodeCa); childId != InvalidNode;) {
796 const NodeId next = topologyOf(dualTree).getNextSibling(childId);
797 const NodeId normalizedChild = collapseRemovedRootBranch(dualTree, editor, nodeCa, childId);
800 }
801 childId = next;
802 }
803
805 for (NodeId childId = topologyOf(dualTree).getFirstChild(nodeCa); childId != InvalidNode; childId = topologyOf(dualTree).getNextSibling(childId)) {
806 if ((isMaxtree && nodeAltitude(dualTree, childId) < nodeAltitude(dualTree, candidateRootId)) ||
807 (!isMaxtree && nodeAltitude(dualTree, childId) > nodeAltitude(dualTree, candidateRootId))) {
809 }
810 }
811
812 bool candidateRootChanged = false;
814 if (!topologyOf(dualTree).isRoot(survivingFinalUnionNode)) {
815 disconnect(dualTree, editor, survivingFinalUnionNode, false);
816 }
819 }
820
821 for (NodeId childId = topologyOf(dualTree).getFirstChild(nodeCa); childId != InvalidNode;) {
822 const NodeId next = topologyOf(dualTree).getNextSibling(childId);
823 if (childId != candidateRootId && !topologyOf(dualTree).hasChild(candidateRootId, childId)) {
824 if (!topologyOf(dualTree).isRoot(childId)) {
825 disconnect(dualTree, editor, childId, false);
826 }
829 }
830 childId = next;
831 }
832
833 editor.setRoot(candidateRootId);
834 editor.releaseNode(nodeCa);
835 notifyNodeRemoved(dualTree, nodeCa);
837 markAttributeUpdate(dualTree, candidateRootId);
838 }
839 computeAttributeOnTreeNode(dualTree, candidateRootId);
840 }
841 } else {
842 if (!topologyOf(dualTree).isRoot(finalUnionNode)) {
843 disconnect(dualTree, editor, finalUnionNode, false);
844 }
846 markAttributeUpdate(dualTree, nodeCa);
847 computeAttributeOnTreeNode(dualTree, nodeCa);
848 }
849 }
850
851 absorbRemovedNodes(dualTree, editor, removedNodesPendingAbsorption_);
852 }
853
859 tree_t* getPrimalTree(bool isMaxtree) {
860 return isMaxtree ? mintree_ : maxtree_;
861 }
862
870 void reattachOutsideIntervalChildren(tree_t* tree, editor_t& editor, NodeId targetNodeId, NodeId sourceNodeId) {
871 assert(tree != nullptr);
872 if (sourceNodeId == targetNodeId) {
873 for (NodeId childId = topologyOf(tree).getFirstChild(sourceNodeId); childId != InvalidNode;) {
874 const NodeId next = topologyOf(tree).getNextSibling(childId);
875 if (mergeNodesByLevel_.isMergeNode(childId)) {
876 editor.detach(childId);
877 }
878 childId = next;
879 }
880 return;
881 }
882
883 for (NodeId childId = topologyOf(tree).getFirstChild(sourceNodeId); childId != InvalidNode;) {
884 const NodeId next = topologyOf(tree).getNextSibling(childId);
885 if (mergeNodesByLevel_.isMergeNode(childId)) {
886 editor.detach(childId);
887 }
888 childId = next;
889 }
890 editor.moveChildren(targetNodeId, sourceNodeId);
891 }
892
906 void buildMergedAndNestedCollections(const tree_t& tree, const std::vector<NodeId>& properPartSetC, NodeId nodeCa, altitude_t b, bool isMaxtree) {
907 mergeNodesByLevel_.resetCollection(isMaxtree);
909 climbedNodeMarks_.resetAll();
910 const altitude_t altitudeCa = nodeAltitude(&tree, nodeCa);
911
912 for (NodeId p : properPartSetC) {
913 for (NodeId q : graph_->getNeighborPixels(p)) {
914 if (pixelsInCMarks_.isMarked(static_cast<size_t>(q))) {
915 continue;
916 }
917
918 const NodeId nodeQ = tree.topology().getProperPartOwner(q);
919 if (nodeQ == InvalidNode) {
920 continue;
921 }
922
923 const altitude_t altitudeQ = nodeAltitude(&tree, nodeQ);
924 const bool validSeed = (isMaxtree && altitudeQ >= altitudeCa) || (!isMaxtree && altitudeQ <= altitudeCa);
925 if (!validSeed) {
926 continue;
927 }
928
929 if (mergeNodesByLevel_.markAdjacentSeed(nodeQ)) {
931 NodeId n = nodeQ;
932 while (n != InvalidNode && tree.topology().isAlive(n) && !climbedNodeMarks_.isMarked(static_cast<size_t>(n))) {
933 const altitude_t levelCurrent = nodeAltitude(&tree, n);
935 break;
936 }
937
938 climbedNodeMarks_.mark(static_cast<size_t>(n));
939 nodeSubtree = n;
940
941 if ((isMaxtree && levelCurrent <= b) || (!isMaxtree && levelCurrent >= b)) {
942 mergeNodesByLevel_.addMergeNode(tree, nodeSubtree);
943 } else {
944 NodeId parentId = tree.topology().getNodeParent(nodeSubtree);
945 if (parentId == nodeSubtree) {
947 }
948 if (!(parentId != InvalidNode &&
949 ((isMaxtree && nodeAltitude(&tree, parentId) > b) ||
950 (!isMaxtree && nodeAltitude(&tree, parentId) < b)))) {
951 mergeNodesByLevel_.addFrontierNodeAboveB(nodeSubtree);
952 }
953 }
954
955 const NodeId parentId = tree.topology().getNodeParent(n);
956 if (parentId == n) {
957 break;
958 }
959 n = parentId;
960 }
961 }
962 }
963 }
964 }
965
985 void updateTree(tree_t* dualTree, NodeId subtreeRoot) {
986 assert(dualTree != nullptr);
988 resetAttributeUpdateMarks();
989
990 const bool isMaxtree = dualTree == maxtree_;
991 tree_t* primalTree = getPrimalTree(isMaxtree);
992 assert(primalTree != nullptr);
993 assert(primalTree->topology().isNode(subtreeRoot));
994 assert(primalTree->topology().isAlive(subtreeRoot));
995
996 const NodeId subtreeParentId = primalTree->topology().getNodeParent(subtreeRoot);
998 const altitude_t b = nodeAltitude(primalTree, subtreeParentId);
999
1001 altitudeCa_ = altitude_t{};
1002
1003 properPartSetC_.clear();
1004 properPartSetC_.reserve(64);
1005 pixelsInCMarks_.resetAll();
1006 for (NodeId subtreeNodeId : primalTree->topology().getNodeSubtree(subtreeRoot)) {
1007 for (NodeId p : primalTree->topology().getProperParts(subtreeNodeId)) {
1008 properPartSetC_.push_back(p);
1009 pixelsInCMarks_.mark(static_cast<size_t>(p));
1010
1011 const NodeId nodeP = dualTree->topology().getProperPartOwner(p);
1012 if (nodeP == InvalidNode) {
1013 continue;
1014 }
1015 const altitude_t altitudeP = nodeAltitude(dualTree, nodeP);
1016 if (nodeCa == InvalidNode || ((isMaxtree && altitudeP < altitudeCa_) || (!isMaxtree && altitudeP > altitudeCa_))) {
1017 altitudeCa_ = altitudeP;
1018 nodeCa = nodeP;
1019 }
1020 }
1021 }
1022 if (properPartSetC_.empty()) {
1023 return;
1024 }
1026
1027 buildMergedAndNestedCollections(*dualTree, properPartSetC_, nodeCa, b, isMaxtree);
1028
1029 editor_t editor = dualTree->edit();
1030 altitude_t currentMergeLevel = mergeNodesByLevel_.firstMergeLevel();
1033 removedMarks_.resetAll();
1034 removedNodesPendingAbsorption_.clear();
1035 nodesPendingRemoval_.reserve(mergeNodesByLevel_.getMaxBucketSize());
1036
1037 while (mergeNodesByLevel_.hasMergeLevel() &&
1038 ((isMaxtree && currentMergeLevel > altitudeCa_) || (!isMaxtree && currentMergeLevel < altitudeCa_))) {
1039 auto& nodesAtCurrentLevel = mergeNodesByLevel_.getMergedNodes(currentMergeLevel);
1041 nodesPendingRemoval_.clear();
1042
1044 if (!dualTree->topology().isAlive(nodeId)) {
1045 continue;
1046 }
1047
1049 if (removedMarks_.isMarked(static_cast<size_t>(nodeId))) {
1050 nodesPendingRemoval_.push_back(nodeId);
1051 continue;
1052 }
1054 disconnect(dualTree, editor, currentUnionNode, false);
1055 reattachOutsideIntervalChildren(dualTree, editor, currentUnionNode, currentUnionNode);
1056
1057 for (NodeId pendingNodeId : nodesPendingRemoval_) {
1058 reattachOutsideIntervalChildren(dualTree, editor, currentUnionNode, pendingNodeId);
1059 notifyMoveProperParts(dualTree, currentUnionNode, pendingNodeId);
1060 editor.moveProperParts(currentUnionNode, pendingNodeId);
1061 disconnect(dualTree, editor, pendingNodeId, true);
1062 }
1063 nodesPendingRemoval_.clear();
1064 continue;
1065 }
1066
1067 reattachOutsideIntervalChildren(dualTree, editor, currentUnionNode, nodeId);
1068 notifyMoveProperParts(dualTree, currentUnionNode, nodeId);
1069 editor.moveProperParts(currentUnionNode, nodeId);
1070 disconnect(dualTree, editor, nodeId, true);
1071 }
1072
1074 for (NodeId nodeId : nodesPendingRemoval_) {
1075 if (!dualTree->topology().isAlive(nodeId) || dualTree->topology().isRoot(nodeId)) {
1076 continue;
1077 }
1078 const NodeId parentId = dualTree->topology().getNodeParent(nodeId);
1079 editor.moveChildren(parentId, nodeId);
1080 notifyMoveProperParts(dualTree, parentId, nodeId);
1081 editor.moveProperParts(parentId, nodeId);
1082 disconnect(dualTree, editor, nodeId, true);
1083 }
1084 currentMergeLevel = mergeNodesByLevel_.nextMergeLevel();
1086 continue;
1087 }
1088
1089 if (currentMergeLevel == b) {
1090 moveSelectedProperPartsToNode(dualTree, editor, currentUnionNode, properPartSetC_);
1091 for (NodeId nodeId : mergeNodesByLevel_.getFrontierNodesAboveB()) {
1092 disconnect(dualTree, editor, nodeId, false);
1094 }
1095 }
1096
1098 if (dualTree->topology().isAlive(previousLevelUnionNode) &&
1099 !dualTree->topology().hasChild(currentUnionNode, previousLevelUnionNode)) {
1100 if (!dualTree->topology().isRoot(previousLevelUnionNode)) {
1101 disconnect(dualTree, editor, previousLevelUnionNode, false);
1102 }
1104 }
1105 }
1106
1107 markAttributeUpdate(dualTree, currentUnionNode);
1108 computeAttributeOnTreeNode(dualTree, currentUnionNode);
1109
1111 currentMergeLevel = mergeNodesByLevel_.nextMergeLevel();
1112 }
1113
1114 finalizeUpdateTreeAndContractRemovedNodes(dualTree, editor, nodeCa, previousLevelUnionNode);
1115 editor.commitUnchecked();
1116 }
1117
1118public:
1122 static constexpr bool usesDenseLevelBackend() { return use_dense_levels; }
1123
1127 static constexpr int denseLevelBackendMaxBits() {
1128 return MMCFILTERS_COMPONENT_TREE_ADJUSTMENT_DENSE_MAX_BITS;
1129 }
1130
1143 : mintree_(mintree),
1144 maxtree_(maxtree),
1145 graph_(&graph),
1146 mergeNodesByLevel_(std::max(mintree ? mintree->topology().getNumInternalNodeSlots() : 0,
1147 maxtree ? maxtree->topology().getNumInternalNodeSlots() : 0)),
1148 removedMarks_(static_cast<size_t>(std::max(mintree ? mintree->topology().getNumInternalNodeSlots() : 0,
1149 maxtree ? maxtree->topology().getNumInternalNodeSlots() : 0))),
1150 pixelsInCMarks_(static_cast<size_t>(std::max(mintree ? mintree->topology().getNumTotalProperParts() : 0,
1151 maxtree ? maxtree->topology().getNumTotalProperParts() : 0))),
1152 climbedNodeMarks_(static_cast<size_t>(std::max(mintree ? mintree->topology().getNumInternalNodeSlots() : 0,
1153 maxtree ? maxtree->topology().getNumInternalNodeSlots() : 0))),
1154 attributeUpdateMarks_(static_cast<size_t>(std::max(mintree ? mintree->topology().getNumInternalNodeSlots() : 0,
1155 maxtree ? maxtree->topology().getNumInternalNodeSlots() : 0))) {
1156 assert(mintree_ != nullptr);
1157 assert(maxtree_ != nullptr);
1158 assert(graph_ != nullptr);
1159 }
1160
1169 std::vector<double>& bufferMin,
1170 std::vector<double>& bufferMax) {
1171 attributeComputerMin_ = &computerMin;
1172 attributeComputerMax_ = &computerMax;
1173 attributeBufferMin_ = &bufferMin;
1174 attributeBufferMax_ = &bufferMax;
1175 }
1176
1181 std::vector<double>& bufferMin,
1182 std::vector<double>& bufferMax) {
1184 }
1185
1193 void pruneMaxTreeAndUpdateMinTree(const std::vector<NodeId>& nodesToPrune) {
1194 assert(mintree_ != nullptr);
1195 assert(maxtree_ != nullptr);
1197 if (rootSubtree == InvalidNode ||
1198 rootSubtree == maxtree_->topology().getRoot() ||
1199 !maxtree_->topology().isNode(rootSubtree) ||
1200 !maxtree_->topology().isAlive(rootSubtree)) {
1201 continue;
1202 }
1203 updateTree(mintree_, rootSubtree);
1204 maxtree_->pruneNode(rootSubtree);
1205 }
1206 }
1207
1215 void pruneMinTreeAndUpdateMaxTree(const std::vector<NodeId>& nodesToPrune) {
1216 assert(mintree_ != nullptr);
1217 assert(maxtree_ != nullptr);
1219 if (rootSubtree == InvalidNode ||
1220 rootSubtree == mintree_->topology().getRoot() ||
1221 !mintree_->topology().isNode(rootSubtree) ||
1222 !mintree_->topology().isAlive(rootSubtree)) {
1223 continue;
1224 }
1225 updateTree(maxtree_, rootSubtree);
1226 mintree_->pruneNode(rootSubtree);
1227 }
1228 }
1229};
1230
1231} // namespace mmcfilters::adjust
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
Two-dimensional adjacency relation with configurable radius and efficient iteration.
AdjacencyRelation & getNeighborPixels(int row, int col)
Prepares iteration over valid neighbouring pixels, excluding the origin.
Mutable morphological tree built directly on proper parts and dense node ids.
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 getNodeParent(NodeId nodeId) const
Returns the direct parent of nodeId.
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.
DualMinMaxTreeIncrementalFilter(tree_t *mintree, tree_t *maxtree, AdjacencyRelation &graph)
Creates an adjuster for externally owned min/max-tree states.
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.
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.