mmcfilters
Public API documentation
Loading...
Searching...
No Matches
ExtinctionValues.hpp
1#pragma once
2
3#include "../trees/TreeAltitudeAlgorithms.hpp"
4#include "../trees/ValuedMorphologicalTree.hpp"
5#include "../trees/ValuedMorphologicalTreeView.hpp"
6#include "../trees/detail/CommittedTreeAccess.hpp"
7#include "../trees/detail/HierarchyCapabilityValidation.hpp"
8#include "../trees/saliency/HierarchySaliencyMapValidation.hpp"
9#include "../trees/saliency/HierarchySaliencyMap.hpp"
10#include "../trees/saliency/HierarchicalWatershedSaliency.hpp"
11#include "../utils/Image.hpp"
12#include "../utils/Common.hpp"
13#include "../utils/Contract.hpp"
14#include "../contours/ContourComputation.hpp"
15
16#include <algorithm>
17#include <cmath>
18#include <concepts>
19#include <limits>
20#include <memory>
21#include <span>
22#include <stack>
23#include <stdexcept>
24#include <string>
25#include <utility>
26#include <vector>
27
28namespace mmcfilters {
29
53
61template <std::floating_point Real = float> class ExtinctionSelectionPolicy {
63 enum class Mode { TopK, MinimumExtinction };
64
66 Mode mode_ = Mode::TopK;
68 int extremaToKeep_ = 0;
70 Real threshold_ = Real{0};
71
72 public:
82 policy.mode_ = Mode::TopK;
83 policy.extremaToKeep_ = extremaToKeep;
84 return policy;
85 }
86
96 policy.mode_ = Mode::MinimumExtinction;
97 policy.threshold_ = threshold;
98 return policy;
99 }
100
106 [[nodiscard]] bool selectsTopK() const noexcept { return mode_ == Mode::TopK; }
107
114 [[nodiscard]] int extremaToKeep() const noexcept { return extremaToKeep_; }
115
122 [[nodiscard]] Real minimumExtinction() const noexcept { return threshold_; }
123};
124
128enum class ExtinctionContourScorePolicy {
130 RankScore,
132 ExtinctionValue
133};
134
173template <AltitudeValue T, std::floating_point Real = float> class ExtinctionValues {
174 protected:
176
178
180 std::vector<RegionalExtremaNode<Real>> regionalExtremaNodes_;
182 std::vector<Real> extinctionValueAttribute_;
186 const ValuedMorphologicalTree<T>* valuedTree_ = nullptr;
188 const MorphologicalTree& tree;
190 std::size_t treeMutationVersion_ = 0;
191
193 struct SelectedExtremum {
195 const RegionalExtremaNode<Real>* record = nullptr;
197 int rankScore = 0;
198 };
199
210 [[nodiscard]] AltitudeView view() const { return valuedTree_ != nullptr ? valuedTree_->asView() : view_; }
211
221 void requireStableTree(const char* context) const { tree.requireMutationVersion(treeMutationVersion_, context); }
222
230 static void requireAttributePointer(const Real* attr, const char* context) {
232 throw std::invalid_argument(std::string(context) + " requires a non-null attribute buffer."));
233 }
234
245 static const Real* requireAttributeBuffer(const MorphologicalTree& tree, const std::vector<Real>& attr, const char* context) {
246 MMCFILTERS_CONTRACT_REQUIRE(attr.size() == static_cast<std::size_t>(tree.numInternalNodeSlots()),
247 throw std::invalid_argument(std::string(context) + " attribute size must match the internal node slot count."));
248 return attr.data();
249 }
250
261 static T altitudeOf(const AltitudeView& view, NodeId nodeId) noexcept {
262 return view.nodeAltitudes()[static_cast<std::size_t>(nodeId)];
263 }
264
276 std::vector<NodeId> collectComponentTreeExtrema() const { return this->tree.leaves(); }
277
287 static Real dominantExtremumSentinel() noexcept { return std::numeric_limits<Real>::max(); }
288
296 static void requireNonNegativeExtremaToKeep(int extremaToKeep, const char* context) {
297 MMCFILTERS_CONTRACT_REQUIRE(extremaToKeep >= 0,
298 throw std::invalid_argument(std::string(context) + " requires a non-negative extremaToKeep value."));
299 }
300
308 static void requireFiniteThreshold(Real threshold, const char* context) {
310 throw std::invalid_argument(std::string(context) + " requires a finite extinction threshold."));
311 }
312
325 if (policy.selectsTopK()) {
327 } else {
328 requireFiniteThreshold(policy.minimumExtinction(), context);
329 }
330 }
331
344 static void requireFormalExtinctionValue(Real value, NodeId nodeId, const char* context) {
345 if (!std::isfinite(value)) {
346 throw std::invalid_argument(std::string(context) + " requires finite extinction values.");
347 }
348 if (value < Real{0}) {
349 throw std::invalid_argument(std::string(context) + " requires non-negative extinction values.");
350 }
351 (void)nodeId;
352 }
353
366 extinctionValueAttribute_.assign(static_cast<std::size_t>(tree.numInternalNodeSlots()), Real{0});
368 Real& localValue = extinctionValueAttribute_[static_cast<std::size_t>(record.leaf)];
369 if (localValue < record.extinction) {
370 localValue = record.extinction;
371 }
372 }
373
374 for (NodeId nodeId : tree.postOrder()) {
375 Real& nodeValue = extinctionValueAttribute_[static_cast<std::size_t>(nodeId)];
376 for (NodeId childId : detail::CommittedTreeAccess::children(tree, nodeId)) {
377 const Real childValue = extinctionValueAttribute_[static_cast<std::size_t>(childId)];
378 if (nodeValue < childValue) {
380 }
381 }
382 }
383 }
384
395 void validateExtinctionValueAttribute(const char* context) const {
398 }
400 HierarchyValuationPolicy::AllowLevelCollapse,
401 HierarchyValuationRangePolicy::RequireNonNegative, context);
402 }
403
418 [[nodiscard]] std::vector<SelectedExtremum> selectExtrema(const ExtinctionSelectionPolicy<Real>& policy, const char* context) const {
420
421 std::vector<const RegionalExtremaNode<Real>*> records;
422 if (policy.selectsTopK()) {
423 const int extremaToSelect = std::min(policy.extremaToKeep(), static_cast<int>(regionalExtremaNodes_.size()));
424 records.reserve(static_cast<std::size_t>(extremaToSelect));
425 for (int i = 0; i < extremaToSelect; ++i) {
426 records.push_back(&regionalExtremaNodes_[static_cast<std::size_t>(i)]);
427 }
428 } else {
429 records.reserve(regionalExtremaNodes_.size());
431 if (record.extinction >= policy.minimumExtinction()) {
432 records.push_back(&record);
433 }
434 }
435 }
436
437 std::vector<SelectedExtremum> selected;
438 selected.reserve(records.size());
439 const int selectedCount = static_cast<int>(records.size());
440 for (int i = 0; i < selectedCount; ++i) {
441 selected.push_back(SelectedExtremum{records[static_cast<std::size_t>(i)], selectedCount - i});
442 }
443 return selected;
444 }
445
460 requireStableTree(context);
461 const AltitudeView altitudeView = view();
462 for (NodeId nodeId : tree.postOrder()) {
463 if (!tree.isRoot(nodeId) && extremumPreservationMask[static_cast<std::size_t>(nodeId)]) {
464 extremumPreservationMask[static_cast<std::size_t>(detail::CommittedTreeAccess::nodeParent(tree, nodeId))] = true;
465 }
466 }
467
469 auto imgOutput = imgOutputPtr->rawData();
470 std::stack<NodeId> stack;
471 stack.push(tree.root());
472 while (!stack.empty()) {
473 const NodeId nodeId = stack.top();
474 stack.pop();
476 for (PixelId pixel : detail::CommittedTreeAccess::properParts(tree, nodeId)) {
477 imgOutput[pixel] = level;
478 }
479 for (NodeId childNodeId : detail::CommittedTreeAccess::children(tree, nodeId)) {
480 if (extremumPreservationMask[static_cast<std::size_t>(childNodeId)]) {
481 stack.push(childNodeId);
482 } else {
483 for (NodeId subtreeNodeId : detail::CommittedTreeAccess::subtree(tree, childNodeId)) {
484 for (PixelId pixel : detail::CommittedTreeAccess::properParts(tree, subtreeNodeId)) {
485 imgOutput[pixel] = level;
486 }
487 }
488 }
489 }
490 }
491 return imgOutputPtr;
492 }
493
504 [[nodiscard]] ImagePtr<T> filteringFromSelectedExtrema(const std::vector<SelectedExtremum>& selected, const char* context) const {
505 std::vector<uint8_t> extremumPreservationMask(tree.numInternalNodeSlots(), false);
506 for (const SelectedExtremum& item : selected) {
507 extremumPreservationMask[static_cast<std::size_t>(item.record->leaf)] = true;
508 }
510 }
511
533 void initialize(const Real* attr) {
534 requireAttributePointer(attr, "ExtinctionValues");
535 std::vector<NodeId> leaves = collectComponentTreeExtrema();
536 regionalExtremaNodes_.reserve(leaves.size());
537 std::vector<uint8_t> visited(this->tree.numInternalNodeSlots(), false);
538 for (NodeId leafNodeId : leaves) {
539 Real extinction = dominantExtremumSentinel();
541 NodeId parentNodeId = detail::CommittedTreeAccess::nodeParent(this->tree, cutoffNodeId);
542 bool flag = true;
543 while (flag && !this->tree.isRoot(cutoffNodeId)) {
544 if (detail::CommittedTreeAccess::numChildren(this->tree, parentNodeId) > 1) {
545 for (NodeId sonNodeId : detail::CommittedTreeAccess::children(this->tree, parentNodeId)) {
546 if (flag) {
548 flag = false;
549 } else if (sonNodeId != cutoffNodeId && attr[sonNodeId] > attr[cutoffNodeId]) {
550 flag = false;
551 }
552 visited[sonNodeId] = true;
553 }
554 }
555 }
556 if (flag) {
558 parentNodeId = detail::CommittedTreeAccess::nodeParent(this->tree, cutoffNodeId);
559 }
560 }
561 if (!this->tree.isRoot(cutoffNodeId)) {
562 extinction = attr[cutoffNodeId];
563 }
564 regionalExtremaNodes_.emplace_back(leafNodeId, cutoffNodeId, extinction);
565 }
566
567 std::sort(regionalExtremaNodes_.begin(), regionalExtremaNodes_.end(), [](const auto& a, const auto& b) {
568 if (a.extinction != b.extinction) {
569 return a.extinction > b.extinction;
570 }
571 if (a.cutoffNode != b.cutoffNode) {
572 return a.cutoffNode < b.cutoffNode;
573 }
574 return a.leaf < b.leaf;
575 });
577 }
579
580 public:
582 using value_type = Real;
583
594 ExtinctionValues(const AltitudeView& view, const std::shared_ptr<Real[]>& attr) : ExtinctionValues(view, attr.get()) {}
595
606 ExtinctionValues(const AltitudeView& view, const std::vector<Real>& attr)
607 : ExtinctionValues(view, requireAttributeBuffer(view.topology(), attr, "ExtinctionValues")) {}
608
618 ExtinctionValues(const AltitudeView& view, const Real* attr) : view_(view), tree(view_.topology()), treeMutationVersion_(tree.getMutationVersion()) {
619 view_.requireTopologyUnchanged("ExtinctionValues");
620 detail::validateGlobalMonotoneAltitudeOrder(this->tree, "ExtinctionValues");
622 }
623
635 ExtinctionValues(const ValuedMorphologicalTree<T>& valuedTree, const std::shared_ptr<Real[]>& attr) : ExtinctionValues(valuedTree.asView(), attr.get()) {
636 valuedTree_ = &valuedTree;
637 }
638
651 valuedTree_ = &valuedTree;
652 }
653
665
682 constexpr const char* context = "ExtinctionValues::contourMap";
683 requireStableTree(context);
684 const std::vector<SelectedExtremum> selected = selectExtrema(selection, context);
685
686 std::vector<uint8_t> keep(tree.numInternalNodeSlots(), false);
687 std::vector<Real> scoreByNode(tree.numInternalNodeSlots(), Real{0});
688 std::vector<NodeId> keptNodes;
689 for (const SelectedExtremum& item : selected) {
690 const NodeId cutoffNode = item.record->cutoffNode;
691 Real score = Real{0};
692 switch (scorePolicy) {
693 case ExtinctionContourScorePolicy::RankScore:
694 score = static_cast<Real>(item.rankScore);
695 break;
696 case ExtinctionContourScorePolicy::ExtinctionValue:
697 score = item.record->extinction;
698 break;
699 default:
700 throw std::invalid_argument(std::string(context) + " received an unknown extinction contour score policy.");
701 }
702 if (!keep[static_cast<std::size_t>(cutoffNode)]) {
703 keptNodes.push_back(cutoffNode);
704 keep[static_cast<std::size_t>(cutoffNode)] = true;
705 scoreByNode[static_cast<std::size_t>(cutoffNode)] = score;
706 } else if (scoreByNode[static_cast<std::size_t>(cutoffNode)] < score) {
707 scoreByNode[static_cast<std::size_t>(cutoffNode)] = score;
708 }
709 }
710
711 ImagePtr<Real> imgOutputPtr = Image<Real>::create(tree.numRows(), tree.numColumns(), Real{0});
712 auto contourOutput = imgOutputPtr->rawData();
713
714 if (keptNodes.empty()) {
715 return imgOutputPtr;
716 }
717 // Preserve the selected-node overwrite priority independently of the
718 // contour traversal's post-order (shared boundary pixels can overlap).
719 std::vector<std::size_t> priorityByNode(tree.numInternalNodeSlots(), 0);
720 for (std::size_t selectionIndex = 0; selectionIndex < keptNodes.size(); ++selectionIndex) {
721 priorityByNode[static_cast<std::size_t>(keptNodes[selectionIndex])] = selectionIndex + 1;
722 }
723 std::vector<std::size_t> pixelPriority(tree.numPixels(), 0);
724 ContourComputation(tree).forEachContour([&](NodeId node, std::span<const PixelId> pixels) {
725 const auto selectionPriority = priorityByNode[static_cast<std::size_t>(node)];
726 if (selectionPriority == 0) {
727 return;
728 }
729 for (PixelId pixel : pixels) {
730 if (selectionPriority > pixelPriority[static_cast<std::size_t>(pixel)]) {
731 contourOutput[pixel] = scoreByNode[static_cast<std::size_t>(node)];
732 pixelPriority[static_cast<std::size_t>(pixel)] = selectionPriority;
733 }
734 }
735 });
736
737 return imgOutputPtr;
738 }
739
764 [[nodiscard]] const std::vector<Real>& getExtinctionValueAttribute() const {
765 constexpr const char* context = "ExtinctionValues::getExtinctionValueAttribute";
766 requireStableTree(context);
769 }
770
786 const std::vector<Real>& valuation = getExtinctionValueAttribute();
787 return HierarchySaliencyMapValidation::rankHierarchyValuation(tree, std::span<const Real>(valuation), HierarchyValuationPolicy::AllowLevelCollapse);
788 }
789
815 const std::vector<Real>& valuation = getExtinctionValueAttribute();
816 return HierarchicalWatershedSaliency::compute(view(), std::span<const Real>(valuation), adjacency);
817 }
818
832 return computeFormalSaliencyEdgeMap(HierarchySaliencyMap::requireProjectionAdjacency(tree, "ExtinctionValues::computeFormalSaliencyEdgeMap"));
833 }
834
849 const std::vector<Real>& valuation = getExtinctionValueAttribute();
850 return HierarchicalWatershedSaliency::computeRanked(view(), std::span<const Real>(valuation), adjacency);
851 }
852
866 return computeRankedFormalSaliencyEdgeMap(
867 HierarchySaliencyMap::requireProjectionAdjacency(tree, "ExtinctionValues::computeRankedFormalSaliencyEdgeMap"));
868 }
869
885 const std::vector<Real>& valuation = getExtinctionValueAttribute();
886 return HierarchySaliencyMap::computeSaliencyEdgeMap(tree, adjacency, std::span<const Real>(valuation), HierarchyValuationPolicy::AllowLevelCollapse);
887 }
888
898 return computeMonotoneExtinctionProjection(
899 HierarchySaliencyMap::requireProjectionAdjacency(tree, "ExtinctionValues::computeMonotoneExtinctionProjection"));
900 }
901
912 const std::vector<Real>& valuation = getExtinctionValueAttribute();
913 return HierarchySaliencyMap::computeCanonicalRankedSaliencyEdgeMap(tree, adjacency, std::span<const Real>(valuation),
914 HierarchyValuationPolicy::AllowLevelCollapse);
915 }
916
926 return computeRankedMonotoneExtinctionProjection(
927 HierarchySaliencyMap::requireProjectionAdjacency(tree, "ExtinctionValues::computeRankedMonotoneExtinctionProjection"));
928 }
929
940 constexpr const char* context = "ExtinctionValues::filtering";
941 const std::vector<SelectedExtremum> selected = selectExtrema(selection, context);
943 }
944
951 [[nodiscard]] const std::vector<RegionalExtremaNode<Real>>& getRegionalExtrema() const {
952 requireStableTree("ExtinctionValues::getRegionalExtrema");
954 }
955};
956
957} // namespace mmcfilters
#define MMCFILTERS_CONTRACT_REQUIRE(condition,...)
Evaluates a caller precondition and its failure action only in checked builds.
Definition Contract.hpp:53
Incremental foreground A4 contours on the image domain.
void forEachContour(Consumer &&consumer) const
Calls consumer(node, pixels) once for every live node.
Explicit extinction-extrema selection policy.
int extremaToKeep() const noexcept
Maximum number of ranked extrema retained by a top-k policy.
bool selectsTopK() const noexcept
Returns true when the policy selects the first ranked extrema.
static ExtinctionSelectionPolicy byThreshold(Real threshold) noexcept
Select every extremum whose extinction value is at least threshold.
static ExtinctionSelectionPolicy byTopK(int extremaToKeep) noexcept
Select the strongest extrema by decreasing extinction ranking.
Real minimumExtinction() const noexcept
Minimum accepted extinction value for a threshold policy.
Computes and stores extinction values for regional extrema.
ExtinctionValues(const AltitudeView &view, const std::shared_ptr< Real[]> &attr)
Computes extinction values from a valued-tree view and shared attribute buffer.
EdgeSaliencyMap< Real > computeMonotoneExtinctionProjection(const RegularGridAdjacency2D &adjacency) const
Projects the max-descendant extinction attribute directly by LCA.
ExtinctionValues(const ValuedMorphologicalTree< T > &valuedTree, const std::vector< Real > &attr)
Computes extinction values from a valued tree and vector attribute buffer.
ExtinctionValues(const ValuedMorphologicalTree< T > &valuedTree, const std::shared_ptr< Real[]> &attr)
Computes extinction values from a valued tree and shared attribute buffer.
const std::vector< RegionalExtremaNode< Real > > & getRegionalExtrema() const
Returns regional-extremum records sorted by decreasing extinction.
EdgeSaliencyMap< int > computeRankedMonotoneExtinctionProjection(const RegularGridAdjacency2D &adjacency) const
Computes canonical effective-edge ranks for the monotone projection.
ImagePtr< T > filtering(const ExtinctionSelectionPolicy< Real > &selection) const
Reconstructs an image from selected regional extrema.
EdgeSaliencyMap< Real > computeFormalSaliencyEdgeMap(const RegularGridAdjacency2D &adjacency) const
Computes the formal hierarchical-watershed extinction saliency map.
EdgeSaliencyMap< int > computeRankedFormalSaliencyEdgeMap(const RegularGridAdjacency2D &adjacency) const
Computes a ranked formal extinction saliency edge map.
Real value_type
Scalar type used for input attributes and extinction values.
ExtinctionValues(const ValuedMorphologicalTree< T > &valuedTree, const Real *attr)
Computes extinction values from a valued tree and raw attribute buffer.
EdgeSaliencyMap< int > computeRankedFormalSaliencyEdgeMap() const
Computes a ranked formal extinction saliency edge map using stored adjacency.
std::vector< int > computeRankedExtinctionValueAttribute() const
Builds a dense integer extinction attribute from extinction levels.
ExtinctionValues(const AltitudeView &view, const Real *attr)
Computes extinction values from a valued-tree view and raw attribute buffer.
EdgeSaliencyMap< int > computeRankedMonotoneExtinctionProjection() const
Stored-adjacency overload of computeRankedMonotoneExtinctionProjection.
EdgeSaliencyMap< Real > computeFormalSaliencyEdgeMap() const
Computes the formal extinction saliency edge map using stored adjacency.
ExtinctionValues(const AltitudeView &view, const std::vector< Real > &attr)
Computes extinction values from a valued-tree view and vector attribute buffer.
ImagePtr< Real > contourMap(const ExtinctionSelectionPolicy< Real > &selection, ExtinctionContourScorePolicy scorePolicy) const
Builds a contour-valued image from selected extinction events.
const std::vector< Real > & getExtinctionValueAttribute() const
Returns extinction values extended from extrema to every hierarchy node.
EdgeSaliencyMap< Real > computeMonotoneExtinctionProjection() const
Stored-adjacency overload of computeMonotoneExtinctionProjection.
static EdgeSaliencyMap< Real > compute(const ValuedMorphologicalTreeView< T > &valuedTree, std::span< const Real > leafExtinction, const RegularGridAdjacency2D &adjacency)
Computes the full-graph extinction hierarchical-watershed saliency.
static EdgeSaliencyMap< int > computeRanked(const ValuedMorphologicalTreeView< T > &valuedTree, std::span< const Real > leafExtinction, const RegularGridAdjacency2D &adjacency)
Computes the canonical dense rank scale of compute.
static void validateHierarchyValuation(const MorphologicalTree &tree, std::span< const Value > valuation, HierarchyValuationPolicy policy=HierarchyValuationPolicy::AllowLevelCollapse, HierarchyValuationRangePolicy rangePolicy=HierarchyValuationRangePolicy::AllowAnyFinite, const char *context="HierarchySaliencyMapValidation::validateHierarchyValuation")
Validates that a node-indexed valuation is compatible with a hierarchy.
static std::vector< int > rankHierarchyValuation(const MorphologicalTree &tree, std::span< const Value > valuation, HierarchyValuationPolicy policy=HierarchyValuationPolicy::AllowLevelCollapse)
Converts a compatible valuation to dense non-negative integer levels.
static EdgeSaliencyMap< int > computeCanonicalRankedSaliencyEdgeMap(const MorphologicalTree &tree, const RegularGridAdjacency2D &adjacency, std::span< const Value > valuation, HierarchyValuationPolicy policy=HierarchyValuationPolicy::AllowLevelCollapse, HierarchyConnectivityPolicy connectivityPolicy=HierarchyConnectivityPolicy::ValidateConnected)
Computes the canonical dense integer saliency scale of a valuation.
static RegularGridAdjacency2D requireProjectionAdjacency(const MorphologicalTree &tree, const char *context="HierarchySaliencyMap::requireProjectionAdjacency")
Returns the unambiguous adjacency stored by a hierarchy.
static EdgeSaliencyMap< Value > computeSaliencyEdgeMap(const MorphologicalTree &tree, const RegularGridAdjacency2D &adjacency, std::span< const Value > valuation, HierarchyValuationPolicy policy=HierarchyValuationPolicy::AllowLevelCollapse, HierarchyLevelConvention levelConvention=HierarchyLevelConvention::EdgeSaliencyValue, HierarchyConnectivityPolicy connectivityPolicy=HierarchyConnectivityPolicy::ValidateConnected)
Computes the formal edge-indexed saliency map induced by a valuation.
static Ptr create(int rows, int columns)
Creates an owned image with uninitialised pixel values.
Definition Image.hpp:105
Mutable connected-subset tree on a finite pixel domain.
int numRows() const
Returns the number of rows in the regular 2D pixel domain.
int numInternalNodeSlots() const
Returns the size of the dense internal-node id domain.
PostOrderNodeRange postOrder() const
Returns a post-order traversal range rooted at the connected root.
void requireMutationVersion(std::size_t expectedVersion, const char *context) const
Rejects stale read-only views that captured an older mutation version.
bool isRoot(NodeId nodeId) const
Tests whether nodeId is the current root.
std::vector< NodeId > leaves() const
Returns all live leaf nodes in the current hierarchy.
int numColumns() const
Returns the number of columns in the regular 2D pixel domain.
NodeId root() const
Returns the current hierarchy root.
Immutable regular-grid 2D adjacency with allocation-free traversal.
Owning result for one computed scalar attribute layout and buffer.
Record describing one regional extremum and its extinction value.
RegionalExtremaNode(NodeId leaf, NodeId cutoffNode, Real extinction)
Builds one extinction-value record.
Real extinction
Attribute value at cutoffNode, or numeric_limits<Real>::max() for the dominant extremum that survives...
NodeId cutoffNode
Highest node retained before the extremum merges with a stronger branch.
NodeId leaf
Leaf node that represents the regional extremum in a max-tree/min-tree.