MorphologicalAttributeFilters
Public API documentation
Loading...
Searching...
No Matches
HierarchicalWatershedSaliency.hpp
1#pragma once
2
3#include "HierarchySaliencyMap.hpp"
4#include "../MorphologicalTreeFactory.hpp"
5#include "../TreeAltitudeAlgorithms.hpp"
6#include "../ValuedMorphologicalTreeView.hpp"
7#include "../detail/HierarchyCapabilityValidation.hpp"
8
9#include <algorithm>
10#include <cmath>
11#include <concepts>
12#include <numeric>
13#include <span>
14#include <stdexcept>
15#include <string>
16#include <utility>
17#include <vector>
18
19namespace mmcfilters {
20
50 private:
52 class DisjointSet {
54 std::vector<NodeId> parent_;
56 std::vector<int> size_;
57
58 public:
64 explicit DisjointSet(int count) : parent_(static_cast<std::size_t>(count)), size_(static_cast<std::size_t>(count), 1) {
65 std::iota(parent_.begin(), parent_.end(), NodeId{0});
66 }
67
74 [[nodiscard]] NodeId find(NodeId id) {
75 NodeId root = id;
76 while (parent_[static_cast<std::size_t>(root)] != root) {
77 root = parent_[static_cast<std::size_t>(root)];
78 }
79 while (parent_[static_cast<std::size_t>(id)] != id) {
80 const NodeId next = parent_[static_cast<std::size_t>(id)];
81 parent_[static_cast<std::size_t>(id)] = root;
82 id = next;
83 }
84 return root;
85 }
86
94 [[nodiscard]] NodeId unite(NodeId lhs, NodeId rhs) {
95 lhs = find(lhs);
96 rhs = find(rhs);
97 if (lhs == rhs) {
98 return lhs;
99 }
100 if (size_[static_cast<std::size_t>(lhs)] < size_[static_cast<std::size_t>(rhs)]) {
101 std::swap(lhs, rhs);
102 }
103 parent_[static_cast<std::size_t>(rhs)] = lhs;
104 size_[static_cast<std::size_t>(lhs)] += size_[static_cast<std::size_t>(rhs)];
105 return lhs;
106 }
107 };
108
110 template <AltitudeValue T> struct OrderedGraphEdge {
112 NodeId source = InvalidNode;
114 NodeId target = InvalidNode;
116 NodeId lca = InvalidNode;
118 T altitude{};
120 bool finestRegionEdge = false;
122 std::size_t order = 0;
123 };
124
126 template <AltitudeValue T, std::floating_point Real> struct PersistenceEdge {
128 OrderedGraphEdge<T> graphEdge;
130 Real persistence = Real{0};
131 };
132
145 template <AltitudeValue T> static bool edgePrecedes(const OrderedGraphEdge<T>& lhs, const OrderedGraphEdge<T>& rhs, NodeAltitudeOrder nodeAltitudeOrder) {
146 if (lhs.finestRegionEdge != rhs.finestRegionEdge) {
147 return lhs.finestRegionEdge;
148 }
149 if (lhs.altitude != rhs.altitude) {
150 if (nodeAltitudeOrder == NodeAltitudeOrder::Increasing) {
151 return rhs.altitude < lhs.altitude;
152 }
153 return lhs.altitude < rhs.altitude;
154 }
155 if (lhs.source != rhs.source) {
156 return lhs.source < rhs.source;
157 }
158 return lhs.target < rhs.target;
159 }
160
174 template <std::floating_point Real>
175 static void validateLeafExtinctions(const MorphologicalTree& tree, std::span<const Real> leafExtinction, const char* context) {
176 if (leafExtinction.size() != static_cast<std::size_t>(tree.numInternalNodeSlots())) {
177 throw std::invalid_argument(std::string(context) + " requires one extinction slot per internal NodeId.");
178 }
179 for (NodeId leaf : tree.leaves()) {
180 const Real value = leafExtinction[static_cast<std::size_t>(leaf)];
181 if (!std::isfinite(value) || value < Real{0}) {
182 throw std::invalid_argument(std::string(context) + " requires finite non-negative leaf extinction values.");
183 }
184 }
185 }
186
197 template <AltitudeValue T>
198 static std::vector<OrderedGraphEdge<T>> collectOrderedGraphEdges(const ValuedMorphologicalTreeView<T>& valuedTree, const RegularGridAdjacency2D& adjacency,
199 const char* context) {
200 const MorphologicalTree& tree = valuedTree.topology();
201 std::vector<OrderedGraphEdge<T>> edges;
202 std::size_t order = 0;
203 for (NodeId source = 0; source < tree.numPixels(); ++source) {
204 const NodeId sourceSmallestNode = tree.smallestNode(source);
205 for (int targetValue : adjacency.getForwardNeighborIndices(source)) {
206 const NodeId target = static_cast<NodeId>(targetValue);
207 const NodeId targetSmallestNode = tree.smallestNode(target);
208 const bool finestRegionEdge = sourceSmallestNode == targetSmallestNode;
210 if (lca == InvalidNode || !tree.isAlive(lca)) {
211 throw std::runtime_error(std::string(context) + " could not find a live LCA for an adjacency edge.");
212 }
213 edges.push_back(OrderedGraphEdge<T>{source, target, lca, valuedTree.nodeAltitude(lca), finestRegionEdge, order++});
214 }
215 }
216 const NodeAltitudeOrder nodeAltitudeOrder = tree.nodeAltitudeOrder();
217 std::stable_sort(edges.begin(), edges.end(), [nodeAltitudeOrder](const auto& lhs, const auto& rhs) { return edgePrecedes(lhs, rhs, nodeAltitudeOrder); });
218 return edges;
219 }
220
231 template <AltitudeValue T>
232 static std::vector<OrderedGraphEdge<T>> selectMinimumSpanningTree(std::vector<OrderedGraphEdge<T>> orderedEdges, int numVertices, const char* context) {
233 DisjointSet components(numVertices);
234 std::vector<OrderedGraphEdge<T>> mst;
235 mst.reserve(static_cast<std::size_t>(std::max(0, numVertices - 1)));
236 for (const OrderedGraphEdge<T>& edge : orderedEdges) {
237 if (components.find(edge.source) == components.find(edge.target)) {
238 continue;
239 }
240 static_cast<void>(components.unite(edge.source, edge.target));
241 mst.push_back(edge);
242 if (mst.size() == static_cast<std::size_t>(numVertices - 1)) {
243 break;
244 }
245 }
246 if (numVertices > 0 && mst.size() != static_cast<std::size_t>(numVertices - 1)) {
247 throw std::invalid_argument(std::string(context) + " requires a connected projection graph.");
248 }
249 return mst;
250 }
251
266 template <AltitudeValue T, std::floating_point Real>
267 static std::vector<PersistenceEdge<T, Real>> assignPersistence(const MorphologicalTree& tree, const std::vector<OrderedGraphEdge<T>>& mst,
268 std::span<const Real> leafExtinction) {
269 const int numVertices = tree.numPixels();
270 std::vector<Real> componentExtinction(static_cast<std::size_t>(numVertices), Real{0});
271 for (NodeId leaf : tree.leaves()) {
272 const auto properParts = tree.properPart(leaf);
273 const auto it = properParts.begin();
274 if (it == properParts.end()) {
275 throw std::invalid_argument("HierarchicalWatershedSaliency requires every component-tree leaf to own a proper part.");
276 }
277 componentExtinction[static_cast<std::size_t>(*it)] = leafExtinction[static_cast<std::size_t>(leaf)];
278 }
279
280 DisjointSet components(numVertices);
281 std::vector<PersistenceEdge<T, Real>> persistenceEdges;
282 persistenceEdges.reserve(mst.size());
283 for (const OrderedGraphEdge<T>& edge : mst) {
284 const NodeId lhsRoot = components.find(edge.source);
285 const NodeId rhsRoot = components.find(edge.target);
286 const Real lhsExtinction = componentExtinction[static_cast<std::size_t>(lhsRoot)];
287 const Real rhsExtinction = componentExtinction[static_cast<std::size_t>(rhsRoot)];
290 componentExtinction[static_cast<std::size_t>(mergedRoot)] = std::max(lhsExtinction, rhsExtinction);
291 }
292 return persistenceEdges;
293 }
294
308 template <AltitudeValue T, std::floating_point Real>
309 static ValuedMorphologicalTree<Real> buildPersistenceDendrogram(std::vector<PersistenceEdge<T, Real>> persistenceEdges, int rows, int columns,
310 const RegularGridAdjacency2D& adjacency) {
311 const int numVertices = rows * columns;
312 std::stable_sort(persistenceEdges.begin(), persistenceEdges.end(), [](const auto& lhs, const auto& rhs) {
313 if (lhs.persistence != rhs.persistence) {
314 return lhs.persistence < rhs.persistence;
315 }
316 return lhs.graphEdge.order < rhs.graphEdge.order;
317 });
318
319 const int numNodes = numVertices == 0 ? 0 : 2 * numVertices - 1;
320 std::vector<NodeId> parent(static_cast<std::size_t>(numNodes), InvalidNode);
321 std::vector<NodeId> smallestNodeMap(static_cast<std::size_t>(numVertices), InvalidNode);
322 std::vector<Real> altitude(static_cast<std::size_t>(numNodes), Real{0});
323 for (PixelId pixel = 0; pixel < numVertices; ++pixel) {
324 parent[static_cast<std::size_t>(pixel)] = pixel;
325 smallestNodeMap[static_cast<std::size_t>(pixel)] = pixel;
326 }
327
328 DisjointSet components(numVertices);
329 std::vector<NodeId> componentNode(static_cast<std::size_t>(numVertices));
330 std::iota(componentNode.begin(), componentNode.end(), NodeId{0});
333 const NodeId lhsRoot = components.find(edge.graphEdge.source);
334 const NodeId rhsRoot = components.find(edge.graphEdge.target);
335 const NodeId lhsNode = componentNode[static_cast<std::size_t>(lhsRoot)];
336 const NodeId rhsNode = componentNode[static_cast<std::size_t>(rhsRoot)];
337 parent[static_cast<std::size_t>(lhsNode)] = nextNode;
338 parent[static_cast<std::size_t>(rhsNode)] = nextNode;
339 parent[static_cast<std::size_t>(nextNode)] = nextNode;
340 altitude[static_cast<std::size_t>(nextNode)] = edge.persistence;
342 componentNode[static_cast<std::size_t>(mergedRoot)] = nextNode;
343 ++nextNode;
344 }
345
346 const NodeId root = numVertices == 1 ? NodeId{0} : nextNode - 1;
348 MorphologicalTreeKind::Generic, NodeAltitudeOrder::Unconstrained, SharedAdjacencyContext{adjacency}};
349 return MorphologicalTreeFactory::createFromNativeTopology(std::span<const NodeId>(parent), std::span<const NodeId>(smallestNodeMap),
350 std::span<const Real>(altitude), root, rows, columns, std::move(semantics));
351 }
352
353 public:
378 template <AltitudeValue T, std::floating_point Real>
380 const RegularGridAdjacency2D& adjacency) {
381 constexpr const char* context = "HierarchicalWatershedSaliency::compute";
382 valuedTree.requireTopologyUnchanged(context);
383 const MorphologicalTree& tree = valuedTree.topology();
384 detail::validateGlobalMonotoneAltitudeOrder(tree, context);
387 validateLeafExtinctions(tree, leafExtinction, context);
389
390 const int numVertices = tree.numPixels();
391 auto graphEdges = collectOrderedGraphEdges(valuedTree, adjacency, context);
392 auto mst = selectMinimumSpanningTree(std::move(graphEdges), numVertices, context);
393 auto persistenceEdges = assignPersistence(tree, mst, leafExtinction);
394 auto dendrogram = buildPersistenceDendrogram(std::move(persistenceEdges), tree.numRows(), tree.numColumns(), adjacency);
395 return HierarchySaliencyMap::computeSaliencyEdgeMap(dendrogram.topology(), adjacency, dendrogram.nodeAltitudeSpan(),
396 HierarchyValuationPolicy::AllowLevelCollapse, HierarchyLevelConvention::EdgeSaliencyValue,
397 HierarchyConnectivityPolicy::AssumeConnected);
398 }
399
413 template <AltitudeValue T, std::floating_point Real>
418};
419
420} // namespace mmcfilters
constexpr NodeId InvalidNode
Sentinel value used to denote an invalid node identifier.
Definition Common.hpp:34
NodeAltitudeOrder
Global ordering constraint of node altitudes along parent-child arcs.
Builds Cousty-style hierarchical-watershed saliency from extinctions.
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 validateHierarchyConnectivity(const MorphologicalTree &tree, const RegularGridAdjacency2D &adjacency, const char *context="HierarchySaliencyMapValidation::validateHierarchyConnectivity")
Validates that every hierarchy support is connected in adjacency.
static EdgeSaliencyMap< int > rankEdgeSaliencyMap(const EdgeSaliencyMap< Value > &edgeMap)
Densely ranks the values already present in an edge saliency map.
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 ValuedMorphologicalTree< T > createFromNativeTopology(std::span< const NodeId > parent, std::span< const NodeId > smallestNodeMap, std::span< const T > nodeAltitudes, NodeId root, MorphologicalTreeSemantics semantics)
Imports a valued connected-subset tree from native buffers.
Mutable connected-subset tree on a finite pixel domain.
int numRows() const
Returns the number of rows in the regular 2D pixel domain.
ProperPartRange properPart(NodeId nodeId) const
Returns a fail-fast range over the pixels in the proper part of nodeId.
int numInternalNodeSlots() const
Returns the size of the dense internal-node id domain.
bool isAlive(NodeId nodeId) const
Tests whether a node slot currently represents a live node.
NodeAltitudeOrder nodeAltitudeOrder() const noexcept
Returns the global parent-to-child altitude ordering constraint.
int numPixels() const
Returns the cardinality of the pixel domain.
std::vector< NodeId > leaves() const
Returns all live leaf nodes in the current hierarchy.
NodeId smallestNode(PixelId pixel) const
Returns the inclusion-smallest node containing pixel.
NodeId lowestCommonAncestor(NodeId u, NodeId v) const
Returns the lowest common ancestor of u and v.
int numColumns() const
Returns the number of columns in the regular 2D pixel domain.
Immutable regular-grid 2D adjacency with allocation-free traversal.
ForwardNeighborIndexRange getForwardNeighborIndices(int row, int column) const
Returns the directed positive half of the neighbourhood.
static void validateMonotoneNodeAltitudes(const MorphologicalTree &tree, std::span< const T > altitude)
Validates the hierarchy's declared global altitude order.
static void validateFiniteAltitudeValues(std::span< const T > altitude, const char *context)
Rejects non-finite floating-point altitudes in a contiguous input range.
Owning result for one computed scalar attribute layout and buffer.
Immutable scientific interpretation attached to one morphological tree.
Records one adjacency shared by both construction polarities.