mmcfilters
Public API documentation
Loading...
Searching...
No Matches
HierarchicalWatershedSaliency.hpp
1#pragma once
2
3#include "HierarchySaliencyMap.hpp"
4#include "../ValuedMorphologicalTree.hpp"
5#include "../TreeAltitudeAlgorithms.hpp"
6#include "../ValuedMorphologicalTreeView.hpp"
7
8#include <algorithm>
9#include <cmath>
10#include <concepts>
11#include <numeric>
12#include <span>
13#include <stdexcept>
14#include <string>
15#include <utility>
16#include <vector>
17
18namespace mmcfilters {
19
49 private:
51 class DisjointSet {
53 std::vector<NodeId> parent_;
55 std::vector<int> size_;
56
57 public:
63 explicit DisjointSet(int count) : parent_(static_cast<std::size_t>(count)), size_(static_cast<std::size_t>(count), 1) {
64 std::iota(parent_.begin(), parent_.end(), NodeId{0});
65 }
66
73 [[nodiscard]] NodeId find(NodeId id) {
74 NodeId root = id;
75 while (parent_[static_cast<std::size_t>(root)] != root) {
76 root = parent_[static_cast<std::size_t>(root)];
77 }
78 while (parent_[static_cast<std::size_t>(id)] != id) {
79 const NodeId next = parent_[static_cast<std::size_t>(id)];
80 parent_[static_cast<std::size_t>(id)] = root;
81 id = next;
82 }
83 return root;
84 }
85
93 [[nodiscard]] NodeId unite(NodeId lhs, NodeId rhs) {
94 lhs = find(lhs);
95 rhs = find(rhs);
96 if (lhs == rhs) {
97 return lhs;
98 }
99 if (size_[static_cast<std::size_t>(lhs)] < size_[static_cast<std::size_t>(rhs)]) {
100 std::swap(lhs, rhs);
101 }
102 parent_[static_cast<std::size_t>(rhs)] = lhs;
103 size_[static_cast<std::size_t>(lhs)] += size_[static_cast<std::size_t>(rhs)];
104 return lhs;
105 }
106 };
107
109 template <AltitudeValue T> struct OrderedGraphEdge {
111 NodeId source = InvalidNode;
113 NodeId target = InvalidNode;
115 NodeId lca = InvalidNode;
117 T altitude{};
119 bool finestRegionEdge = false;
121 std::size_t order = 0;
122 };
123
125 template <AltitudeValue T, std::floating_point Real> struct PersistenceEdge {
127 OrderedGraphEdge<T> graphEdge;
129 Real persistence = Real{0};
130 };
131
144 template <AltitudeValue T> static bool edgePrecedes(const OrderedGraphEdge<T>& lhs, const OrderedGraphEdge<T>& rhs, NodeAltitudeOrder nodeAltitudeOrder) {
145 if (lhs.finestRegionEdge != rhs.finestRegionEdge) {
146 return lhs.finestRegionEdge;
147 }
148 if (lhs.altitude != rhs.altitude) {
149 if (nodeAltitudeOrder == NodeAltitudeOrder::Increasing) {
150 return rhs.altitude < lhs.altitude;
151 }
152 return lhs.altitude < rhs.altitude;
153 }
154 if (lhs.source != rhs.source) {
155 return lhs.source < rhs.source;
156 }
157 return lhs.target < rhs.target;
158 }
159
173 template <std::floating_point Real>
174 static void validateLeafExtinctions(const MorphologicalTree& tree, std::span<const Real> leafExtinction, const char* context) {
175 if (leafExtinction.size() != static_cast<std::size_t>(tree.numInternalNodeSlots())) {
176 throw std::invalid_argument(std::string(context) + " requires one extinction slot per internal NodeId.");
177 }
178 for (NodeId leaf : tree.leaves()) {
179 const Real value = leafExtinction[static_cast<std::size_t>(leaf)];
180 if (!std::isfinite(value) || value < Real{0}) {
181 throw std::invalid_argument(std::string(context) + " requires finite non-negative leaf extinction values.");
182 }
183 }
184 }
185
194 template <AltitudeValue T>
195 static std::vector<OrderedGraphEdge<T>> collectOrderedGraphEdges(const ValuedMorphologicalTreeView<T>& valuedTree, const RegularGridAdjacency2D& adjacency) {
196 const MorphologicalTree& tree = valuedTree.topology();
197 std::vector<OrderedGraphEdge<T>> edges;
198 std::size_t order = 0;
199 for (NodeId source = 0; source < tree.numPixels(); ++source) {
200 const NodeId sourceSmallestNode = detail::TreeKernelAccess::smallestNodeMap(tree, source);
201 for (int targetValue : detail::CommittedGridAccess::forwardNeighbors(adjacency, source)) {
202 const NodeId target = static_cast<NodeId>(targetValue);
203 const NodeId targetSmallestNode = detail::TreeKernelAccess::smallestNodeMap(tree, target);
204 const bool finestRegionEdge = sourceSmallestNode == targetSmallestNode;
205 const NodeId lca = finestRegionEdge ? sourceSmallestNode : detail::TreeKernelAccess::lowestCommonAncestor(tree, sourceSmallestNode, targetSmallestNode);
206 edges.push_back(OrderedGraphEdge<T>{source, target, lca, valuedTree.nodeAltitudes()[static_cast<std::size_t>(lca)], finestRegionEdge, order++});
207 }
208 }
209 const NodeAltitudeOrder nodeAltitudeOrder = tree.nodeAltitudeOrder();
210 std::stable_sort(edges.begin(), edges.end(), [nodeAltitudeOrder](const auto& lhs, const auto& rhs) { return edgePrecedes(lhs, rhs, nodeAltitudeOrder); });
211 return edges;
212 }
213
223 template <AltitudeValue T>
224 static std::vector<OrderedGraphEdge<T>> selectMinimumSpanningTree(std::vector<OrderedGraphEdge<T>> orderedEdges, int numVertices) {
225 DisjointSet components(numVertices);
226 std::vector<OrderedGraphEdge<T>> mst;
227 mst.reserve(static_cast<std::size_t>(std::max(0, numVertices - 1)));
228 for (const OrderedGraphEdge<T>& edge : orderedEdges) {
229 if (components.find(edge.source) == components.find(edge.target)) {
230 continue;
231 }
232 static_cast<void>(components.unite(edge.source, edge.target));
233 mst.push_back(edge);
234 if (mst.size() == static_cast<std::size_t>(numVertices - 1)) {
235 break;
236 }
237 }
238 return mst;
239 }
240
254 template <AltitudeValue T, std::floating_point Real>
255 static std::vector<PersistenceEdge<T, Real>> assignPersistence(const MorphologicalTree& tree, const std::vector<OrderedGraphEdge<T>>& mst,
256 std::span<const Real> leafExtinction) {
257 const int numVertices = tree.numPixels();
258 std::vector<Real> componentExtinction(static_cast<std::size_t>(numVertices), Real{0});
259 for (NodeId leaf : tree.leaves()) {
260 const auto properParts = detail::TreeKernelAccess::properParts(tree, leaf);
261 const auto it = properParts.begin();
262 componentExtinction[static_cast<std::size_t>(*it)] = leafExtinction[static_cast<std::size_t>(leaf)];
263 }
264
265 DisjointSet components(numVertices);
266 std::vector<PersistenceEdge<T, Real>> persistenceEdges;
267 persistenceEdges.reserve(mst.size());
268 for (const OrderedGraphEdge<T>& edge : mst) {
269 const NodeId lhsRoot = components.find(edge.source);
270 const NodeId rhsRoot = components.find(edge.target);
271 const Real lhsExtinction = componentExtinction[static_cast<std::size_t>(lhsRoot)];
272 const Real rhsExtinction = componentExtinction[static_cast<std::size_t>(rhsRoot)];
275 componentExtinction[static_cast<std::size_t>(mergedRoot)] = std::max(lhsExtinction, rhsExtinction);
276 }
277 return persistenceEdges;
278 }
279
293 template <AltitudeValue T, std::floating_point Real>
294 static ValuedMorphologicalTree<Real> buildPersistenceDendrogram(std::vector<PersistenceEdge<T, Real>> persistenceEdges, int rows, int columns,
295 const RegularGridAdjacency2D& adjacency) {
296 const int numVertices = rows * columns;
297 std::stable_sort(persistenceEdges.begin(), persistenceEdges.end(), [](const auto& lhs, const auto& rhs) {
298 if (lhs.persistence != rhs.persistence) {
299 return lhs.persistence < rhs.persistence;
300 }
301 return lhs.graphEdge.order < rhs.graphEdge.order;
302 });
303
304 const int numNodes = numVertices == 0 ? 0 : 2 * numVertices - 1;
305 std::vector<NodeId> parent(static_cast<std::size_t>(numNodes), InvalidNode);
306 std::vector<NodeId> smallestNodeMap(static_cast<std::size_t>(numVertices), InvalidNode);
307 std::vector<Real> altitude(static_cast<std::size_t>(numNodes), Real{0});
308 for (PixelId pixel = 0; pixel < numVertices; ++pixel) {
309 parent[static_cast<std::size_t>(pixel)] = pixel;
310 smallestNodeMap[static_cast<std::size_t>(pixel)] = pixel;
311 }
312
313 DisjointSet components(numVertices);
314 std::vector<NodeId> componentNode(static_cast<std::size_t>(numVertices));
315 std::iota(componentNode.begin(), componentNode.end(), NodeId{0});
318 const NodeId lhsRoot = components.find(edge.graphEdge.source);
319 const NodeId rhsRoot = components.find(edge.graphEdge.target);
320 const NodeId lhsNode = componentNode[static_cast<std::size_t>(lhsRoot)];
321 const NodeId rhsNode = componentNode[static_cast<std::size_t>(rhsRoot)];
322 parent[static_cast<std::size_t>(lhsNode)] = nextNode;
323 parent[static_cast<std::size_t>(rhsNode)] = nextNode;
324 parent[static_cast<std::size_t>(nextNode)] = nextNode;
325 altitude[static_cast<std::size_t>(nextNode)] = edge.persistence;
327 componentNode[static_cast<std::size_t>(mergedRoot)] = nextNode;
328 ++nextNode;
329 }
330
331 const NodeId root = numVertices == 1 ? NodeId{0} : nextNode - 1;
333 MorphologicalTreeKind::Generic, NodeAltitudeOrder::Unconstrained, SharedAdjacencyContext{adjacency}};
334 return detail::materializeNativeHierarchy<Real>(makeNativeHierarchyOwned(
335 std::move(parent), std::move(smallestNodeMap), std::move(altitude), root, GridDomain2D{rows, columns}, std::move(semantics)));
336 }
337
338 public:
367 template <AltitudeValue T, std::floating_point Real>
369 const RegularGridAdjacency2D& adjacency) {
370 constexpr const char* context = "HierarchicalWatershedSaliency::compute";
371 valuedTree.requireTopologyUnchanged(context);
372 const MorphologicalTree& tree = valuedTree.topology();
374 validateLeafExtinctions(tree, leafExtinction, context);
376
377 const int numVertices = tree.numPixels();
378 auto graphEdges = collectOrderedGraphEdges(valuedTree, adjacency);
379 auto mst = selectMinimumSpanningTree(std::move(graphEdges), numVertices);
380 auto persistenceEdges = assignPersistence(tree, mst, leafExtinction);
381 auto dendrogram = buildPersistenceDendrogram(std::move(persistenceEdges), tree.numRows(), tree.numColumns(), adjacency);
382 return HierarchySaliencyMap::computeSaliencyEdgeMap(dendrogram.topology(), adjacency, dendrogram.nodeAltitudeSpan(),
383 HierarchyValuationPolicy::AllowLevelCollapse, HierarchyLevelConvention::EdgeSaliencyValue,
384 HierarchyConnectivityPolicy::AssumeConnected);
385 }
386
400 template <AltitudeValue T, std::floating_point Real>
405};
406
407} // 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.
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.
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.
int numColumns() const
Returns the number of columns in the regular 2D pixel domain.
Immutable regular-grid 2D adjacency with allocation-free traversal.
static void requireGlobalMonotoneAltitudeOrder(const MorphologicalTree &tree, const char *context)
Requires a declared globally monotone altitude order.
Owning result for one computed scalar attribute layout and buffer.
Shape metadata optionally attached to the pixel domain.
Immutable scientific interpretation attached to one morphological tree.
Records one adjacency shared by both construction polarities.