mmcfilters
Public API documentation
Loading...
Searching...
No Matches
ExtremumResidualTreeByCTsBuilder.hpp
1#pragma once
2
3#include "ComponentTreeBuilder.hpp"
4#include "NativeHierarchyOwned.hpp"
5#include "ValuedMorphologicalTree.hpp"
6#include "detail/DualMinMaxTreeIncrementalFilterLeaf.hpp"
7#include "detail/TreeKernelAccess.hpp"
8#include "../utils/Common.hpp"
9
10#include <cassert>
11#include <cstddef>
12#include <iterator>
13#include <list>
14#include <optional>
15#include <queue>
16#include <span>
17#include <utility>
18#include <vector>
19
20namespace mmcfilters {
21namespace detail {
26template <AltitudeValue T> struct ExtremumResidualTreeTestAccess;
27} // namespace detail
28
40template <AltitudeValue T> class ExtremumResidualTreeByCTsBuilder {
41 public:
47
54 auto minTree = detail::materializeNativeHierarchy<T>(ComponentTreeBuilder(adjacency_, false).build(image), std::nullopt,
55 detail::MaterializedVersionPolicy::PreserveLinkedConstruction);
56 auto maxTree = detail::materializeNativeHierarchy<T>(ComponentTreeBuilder(adjacency_, true).build(image), std::nullopt,
57 detail::MaterializedVersionPolicy::PreserveLinkedConstruction);
58 const auto maxTreeNodeSlots = maxTree.topology().numInternalNodeSlots();
59 const auto minTreeNodeSlots = minTree.topology().numInternalNodeSlots();
60 detail::DualMinMaxTreeIncrementalFilterLeaf<T> updater(&minTree, &maxTree, adjacency_);
61 ExtremumAgenda agenda;
62 SelectedSupport selectedSupport;
63 ResidualTreeConstruction extTree(image->getSize(), maxTreeNodeSlots, minTreeNodeSlots);
64
65 agenda.initialize(maxTree, minTree, updater); // Line 2.
66 while (const auto selected = agenda.next(maxTree, minTree)) { // Lines 3-5.
67 const auto& selectedExtremum = *selected;
68 const bool isMaximum = selectedExtremum.polarity == Polarity::Maximum;
69 ValuedMorphologicalTree<T>& primal = isMaximum ? maxTree : minTree;
70 ValuedMorphologicalTree<T>& dual = isMaximum ? minTree : maxTree;
71 selectedSupport.collectFromLeaf(selectedExtremum.nodeId, primal, dual);
72 const NodeId primalParentNode = detail::TreeKernelAccess::nodeParent(primal.topology(), selectedExtremum.nodeId);
73 const T nodeAltitude = primal.nodeAltitudes()[static_cast<std::size_t>(selectedExtremum.nodeId)];
74
75 // Lines 6-11: add X_s and attach the pending supports contained in X_s as children.
76 const NodeId residualNode = extTree.addSelectedSupport(selectedSupport, selectedExtremum, nodeAltitude);
77
78 // Line 12: apply the elementary leveling.
79 applyElementaryLeveling(updater, isMaximum, selectedExtremum.nodeId, selectedSupport);
80
81 // Line 13: pending supports follow the proper-part transfers.
82 extTree.updatePendingSupports(residualNode, selectedExtremum.polarity, primalParentNode, updater.getLastSelectedSupportNode(),
83 updater.getLastProperPartTransfers());
84
85 const Polarity dualPolarity = oppositePolarity(selectedExtremum.polarity);
86 for (NodeId node : updater.getLastCandidateNodes())
87 agenda.enqueue(dual, dualPolarity, node, updater);
89 }
90
91 // Lines 16-18: attach the remaining pending supports to the terminal root.
92 const T terminalAltitude = minTree.nodeAltitudes()[static_cast<std::size_t>(minTree.topology().root())];
93 assert(minTree.topology().numNodes() == 1 && maxTree.topology().numNodes() == 1);
94 assert(maxTree.nodeAltitudes()[static_cast<std::size_t>(maxTree.topology().root())] == terminalAltitude);
95 return std::move(extTree).completeTree(
96 image, makeMorphologicalTreeSemantics(MorphologicalTreeKind::ExtremumResidualTree, SharedAdjacencyContext{adjacency_}), terminalAltitude);
97 }
98
103 [[nodiscard]] const RegularGridAdjacency2D& adjacency() const noexcept { return adjacency_; }
104
105 private:
106 friend struct detail::ExtremumResidualTreeTestAccess<T>;
110 enum class Polarity {
111 Maximum,
112 Minimum
113 };
119 [[nodiscard]] static constexpr Polarity oppositePolarity(Polarity polarity) {
120 return polarity == Polarity::Maximum ? Polarity::Minimum : Polarity::Maximum;
121 }
122
126 struct ExtremumCandidate {
127 std::size_t supportCardinality = 0;
128 PixelId minimumPixel = InvalidPixel;
129 Polarity polarity;
130 NodeId nodeId;
131 };
135 struct ExtremumCandidatePriority {
142 bool operator()(const ExtremumCandidate& lhs, const ExtremumCandidate& rhs) const {
143 if (lhs.supportCardinality != rhs.supportCardinality)
144 return lhs.supportCardinality > rhs.supportCardinality;
145 return lhs.minimumPixel > rhs.minimumPixel;
146 }
147 };
152 struct ExtremumAgenda {
153 std::priority_queue<ExtremumCandidate, std::vector<ExtremumCandidate>, ExtremumCandidatePriority> candidates;
154
162 void enqueue(const ValuedMorphologicalTree<T>& tree, Polarity polarity, NodeId node, const detail::DualMinMaxTreeIncrementalFilterLeaf<T>& updater) {
163 if (!isNonterminalLeaf(tree, node))
164 return;
165 candidates.push({static_cast<std::size_t>(detail::TreeKernelAccess::properPartCardinality(tree.topology(), node)), updater.minPixel(tree, node),
166 polarity, node});
167 }
168
175 void initialize(const ValuedMorphologicalTree<T>& maxTree, const ValuedMorphologicalTree<T>& minTree,
176 const detail::DualMinMaxTreeIncrementalFilterLeaf<T>& updater) {
177 for (NodeId node : maxTree.topology().aliveNodeIds())
178 enqueue(maxTree, Polarity::Maximum, node, updater);
179 for (NodeId node : minTree.topology().aliveNodeIds())
180 enqueue(minTree, Polarity::Minimum, node, updater);
181 }
182
189 [[nodiscard]] std::optional<ExtremumCandidate> next(const ValuedMorphologicalTree<T>& maxTree, const ValuedMorphologicalTree<T>& minTree) {
190 while (!candidates.empty()) {
191 const ExtremumCandidate selected = candidates.top();
192 candidates.pop();
193 const ValuedMorphologicalTree<T>& primal = selected.polarity == Polarity::Maximum ? maxTree : minTree;
194 if (!isNonterminalLeaf(primal, selected.nodeId))
195 continue;
196 // Flat zones only merge. Equal cardinality and retention of the
197 // recorded minimum certify that the selection key is unchanged.
198 if (static_cast<std::size_t>(detail::TreeKernelAccess::properPartCardinality(primal.topology(), selected.nodeId)) !=
199 selected.supportCardinality)
200 continue;
201 if (detail::TreeKernelAccess::smallestNodeMap(primal.topology(), selected.minimumPixel) != selected.nodeId)
202 continue;
203 return selected;
204 }
205 return std::nullopt;
206 }
207 };
208
212 struct SelectedSupport {
213 std::vector<PixelId> pixels;
214 NodeId dualProperPartNode = InvalidNode;
215 NodeId wholeDualProperPartNode = InvalidNode;
216
223 void collectFromLeaf(NodeId leaf, const ValuedMorphologicalTree<T>& primal, const ValuedMorphologicalTree<T>& dual) {
224 const auto& dualTopology = dual.topology();
225 pixels.clear();
226 wholeDualProperPartNode = InvalidNode;
227 for (PixelId pixel : detail::TreeKernelAccess::properParts(primal.topology(), leaf)) {
228 pixels.push_back(pixel);
229 }
230 // An extremal flat zone is nonempty and lies in one dual proper part.
231 dualProperPartNode = detail::TreeKernelAccess::smallestNodeMap(dualTopology, pixels.front());
232#ifndef NDEBUG
233 for (PixelId pixel : pixels) {
234 assert(detail::TreeKernelAccess::smallestNodeMap(dualTopology, pixel) == dualProperPartNode);
235 }
236#endif
237 if (detail::TreeKernelAccess::properPartCardinality(dualTopology, dualProperPartNode) == static_cast<int>(pixels.size())) {
238 wholeDualProperPartNode = dualProperPartNode;
239 }
240 }
241 };
249 [[gnu::always_inline]] static void applyElementaryLeveling(detail::DualMinMaxTreeIncrementalFilterLeaf<T>& updater, bool isMaximum, NodeId selectedLeafNode,
250 const SelectedSupport& selectedSupport) {
251 if (isMaximum)
252 updater.pruneMaxLeafAndUpdateMinTree(selectedLeafNode, selectedSupport.pixels, selectedSupport.dualProperPartNode,
253 selectedSupport.wholeDualProperPartNode);
254 else
255 updater.pruneMinLeafAndUpdateMaxTree(selectedLeafNode, selectedSupport.pixels, selectedSupport.dualProperPartNode,
256 selectedSupport.wholeDualProperPartNode);
257 }
258
263 class PendingSupportsByProperPart {
264 std::vector<std::list<NodeId>> residualNodesByProperPart_;
265 std::vector<std::list<NodeId>::iterator> positionByResidualNode_;
266
267 public:
272 explicit PendingSupportsByProperPart(std::size_t componentNodeSlots) : residualNodesByProperPart_(componentNodeSlots) {}
273
279 std::list<NodeId>& pendingSupportsIn(NodeId properPartNode) { return residualNodesByProperPart_[static_cast<std::size_t>(properPartNode)]; }
285 const std::list<NodeId>& pendingSupportsIn(NodeId properPartNode) const { return residualNodesByProperPart_[static_cast<std::size_t>(properPartNode)]; }
286
292 void addPendingSupport(NodeId residualNode, NodeId properPartNode) {
293 auto& pendingNodes = pendingSupportsIn(properPartNode);
294 if (static_cast<std::size_t>(residualNode) >= positionByResidualNode_.size())
295 positionByResidualNode_.resize(static_cast<std::size_t>(residualNode) + 1);
296 pendingNodes.push_back(residualNode);
297 positionByResidualNode_[static_cast<std::size_t>(residualNode)] = std::prev(pendingNodes.end());
298 }
299
305 void removePendingSupport(NodeId residualNode, NodeId properPartNode) {
306 pendingSupportsIn(properPartNode).erase(positionByResidualNode_[static_cast<std::size_t>(residualNode)]);
307 }
308
314 void transferPendingSupports(NodeId sourceNode, NodeId targetNode) {
315 auto& targetPendingSupports = pendingSupportsIn(targetNode);
316 targetPendingSupports.splice(targetPendingSupports.end(), pendingSupportsIn(sourceNode));
317 }
318 };
319
324 struct ResidualTreeConstruction {
325 PendingSupportsByProperPart maxTreePendingSupports;
326 PendingSupportsByProperPart minTreePendingSupports;
327 std::vector<NodeId> nodeParents{InvalidNode};
328 std::vector<NodeId> smallestNodeMap;
329 std::vector<T> nodeAltitudes{T{}};
336 ResidualTreeConstruction(std::size_t numPixels, std::size_t maxTreeNodeSlots, std::size_t minTreeNodeSlots)
337 : maxTreePendingSupports(maxTreeNodeSlots), minTreePendingSupports(minTreeNodeSlots), smallestNodeMap(numPixels, InvalidNode) {
338 nodeParents.reserve(numPixels);
339 nodeAltitudes.reserve(numPixels);
340 }
341
349 NodeId addSelectedSupport(const SelectedSupport& selectedSupport, const ExtremumCandidate& selectedExtremum, T supportAltitude) {
350 // Property 6 gives each recorded support a new nonempty proper part;
351 // together with the terminal root this bounds the node count by pixels.
352 PendingSupportsByProperPart& primalPendingSupports = pendingSupportsFor(selectedExtremum.polarity);
353 PendingSupportsByProperPart& dualPendingSupports = pendingSupportsFor(oppositePolarity(selectedExtremum.polarity));
354 auto& pendingChildren = primalPendingSupports.pendingSupportsIn(selectedExtremum.nodeId);
355 const NodeId residualNode = static_cast<NodeId>(nodeParents.size());
356 nodeParents.push_back(InvalidNode); // Add X_s to implicit Xi, line 7.
357 nodeAltitudes.push_back(supportAltitude); // eta(X_s), Eqs. 25, 33.
358 for (PixelId pixel : selectedSupport.pixels) {
359 if (smallestNodeMap[static_cast<std::size_t>(pixel)] == InvalidNode) {
360 smallestNodeMap[static_cast<std::size_t>(pixel)] = residualNode;
361 }
362 }
363 while (!pendingChildren.empty()) {
364 const NodeId childResidualNode = pendingChildren.front();
365 nodeParents[static_cast<std::size_t>(childResidualNode)] = residualNode; // Eq. (32); remove child from implicit Xi, lines 9-10.
366 dualPendingSupports.removePendingSupport(childResidualNode, selectedSupport.dualProperPartNode);
367 primalPendingSupports.removePendingSupport(childResidualNode, selectedExtremum.nodeId);
368 }
369 return residualNode;
370 }
371
381 template <class ProperPartTransfer>
382 void updatePendingSupports(NodeId residualNode, Polarity selectedPolarity, NodeId primalDestinationNode, NodeId dualDestinationNode,
383 std::span<const ProperPartTransfer> dualProperPartTransfers) {
384 PendingSupportsByProperPart& dualPendingSupports = pendingSupportsFor(oppositePolarity(selectedPolarity));
385 // Pending supports follow whole proper-part transfers. The partial transfer
386 // of X_s contains only its consumed children, already removed from Xi_s.
387 for (const auto& transfer : dualProperPartTransfers)
388 dualPendingSupports.transferPendingSupports(transfer.source, transfer.target);
389 // Primal pruning moves the complete leaf to its parent. The dual updater
390 // tracks the complete selected support through pixel moves and whole-part merges.
391 pendingSupportsFor(selectedPolarity).addPendingSupport(residualNode, primalDestinationNode);
392 dualPendingSupports.addPendingSupport(residualNode, dualDestinationNode);
393 }
394
402 [[nodiscard]] NativeHierarchyOwned<T> completeTree(const ImagePtr<T>& image, MorphologicalTreeSemantics treeSemantics, T rootAltitude) && {
403 nodeParents[0] = 0;
404 nodeAltitudes[0] = rootAltitude;
405 // The remaining implicit Xi nodes become children of the terminal root.
406 for (NodeId& parentNode : nodeParents)
407 if (parentNode == InvalidNode)
408 parentNode = 0;
409 for (NodeId& smallestNode : smallestNodeMap) {
410 if (smallestNode == InvalidNode) {
411 smallestNode = 0;
412 }
413 }
414 return makeNativeHierarchyOwned<T>(std::move(nodeParents), std::move(smallestNodeMap), std::move(nodeAltitudes), NodeId{0},
415 GridDomain2D{image->getNumRows(), image->getNumColumns()}, std::move(treeSemantics));
416 }
417
418 private:
424 PendingSupportsByProperPart& pendingSupportsFor(Polarity polarity) {
425 return polarity == Polarity::Maximum ? maxTreePendingSupports : minTreePendingSupports;
426 }
427 };
428
429 RegularGridAdjacency2D adjacency_;
430
437 [[nodiscard]] static bool isNonterminalLeaf(const ValuedMorphologicalTree<T>& tree, NodeId node) {
438 const auto& topology = tree.topology();
439 return detail::TreeKernelAccess::isAlive(topology, node) && node != topology.root() && detail::TreeKernelAccess::numChildren(topology, node) == 0;
440 }
441};
442
443} // namespace mmcfilters
int PixelId
Pixel identifier type used by source and active construction domains.
Definition Common.hpp:26
int NodeId
Node identifier type used throughout the project.
Definition Common.hpp:17
constexpr NodeId InvalidNode
Sentinel value used to denote an invalid node identifier.
Definition Common.hpp:34
constexpr PixelId InvalidPixel
Sentinel value used to denote an invalid pixel identifier.
Definition Common.hpp:43
MorphologicalTreeSemantics makeMorphologicalTreeSemantics(MorphologicalTreeKind kind, MorphologicalTreeConstructionContext constructionContext=NoConstructionContext{})
Constructs semantics using the conventional altitude order of kind.
Builds max-trees and min-trees with union-find.
Algorithm 1 of paper: "The Extremum Residual Tree: A Self-Dual Morphological Tree Based on Levelings"...
NativeHierarchyOwned< T > build(const ImagePtr< T > &image)
Builds temporary min/max trees and records the extremum residual hierarchy.
ExtremumResidualTreeByCTsBuilder(RegularGridAdjacency2D adjacency)
Configures component-tree construction with a shared adjacency and row-major spatial ties.
const RegularGridAdjacency2D & adjacency() const noexcept
Exposes the shared component-tree adjacency.
Immutable regular-grid 2D adjacency with allocation-free traversal.
Owning result for one computed scalar attribute layout and buffer.
Records one adjacency shared by both construction polarities.