MorphologicalAttributeFilters
Public API documentation
Loading...
Searching...
No Matches
HierarchySaliencyMap.hpp
1#pragma once
2
3#include "HierarchySaliencyMapValidation.hpp"
4#include "../../utils/RegularGridAdjacency2D.hpp"
5#include "../../utils/Altitude.hpp"
6#include "../../utils/Common.hpp"
7#include "../MorphologicalTree.hpp"
8#include "../detail/MorphologicalTreeConstructionContextQueries.hpp"
9#include "../ValuedMorphologicalTree.hpp"
10
11#include <algorithm>
12#include <cmath>
13#include <cstddef>
14#include <functional>
15#include <iterator>
16#include <sstream>
17#include <span>
18#include <stdexcept>
19#include <string>
20#include <type_traits>
21#include <utility>
22#include <vector>
23
24namespace mmcfilters {
25
34template <class Value> struct EdgeSaliencyMap {
36 int numRows = 0;
38 int numColumns = 0;
40 double adjacencyRadius = 0.0;
42 std::vector<NodeId> sources;
44 std::vector<NodeId> targets;
46 std::vector<Value> values;
47
53 [[nodiscard]] std::size_t size() const noexcept { return values.size(); }
54
60 [[nodiscard]] bool empty() const noexcept { return values.empty(); }
61};
62
71enum class HierarchyLevelConvention {
72 EdgeSaliencyValue,
73 PartitionAppearanceLevel,
74};
75
102 private:
110 static void validateTreeAndAdjacency(const MorphologicalTree& tree, const RegularGridAdjacency2D& adjacency, const char* context) {
111 detail::requireCommittedRootedHierarchy(tree, context);
112 if (tree.numRows() <= 0 || tree.numColumns() <= 0 || tree.numPixels() <= 0) {
113 throw std::invalid_argument(std::string(context) + " requires a non-empty image/pixel domain.");
114 }
115 if (adjacency.getNumRows() != tree.numRows() || adjacency.getNumColumns() != tree.numColumns()) {
116 std::ostringstream oss;
117 oss << context << " adjacency domain must match the tree image domain; got " << adjacency.getNumRows() << "x" << adjacency.getNumColumns()
118 << " for a tree domain of " << tree.numRows() << "x" << tree.numColumns() << ".";
119 throw std::invalid_argument(oss.str());
120 }
121 }
122
133 static bool sameAdjacencyGraph(const RegularGridAdjacency2D& lhs, const RegularGridAdjacency2D& rhs) noexcept {
134 if (lhs.getNumRows() != rhs.getNumRows() || lhs.getNumColumns() != rhs.getNumColumns() || lhs.getSize() != rhs.getSize()) {
135 return false;
136 }
137 for (int index = 0; index < lhs.getSize(); ++index) {
138 if (lhs.getOffsetRow(index) != rhs.getOffsetRow(index) || lhs.getOffsetColumn(index) != rhs.getOffsetColumn(index)) {
139 return false;
140 }
141 }
142 return true;
143 }
144
152 static RegularGridAdjacency2D requireStoredAdjacency(const MorphologicalTree& tree, const char* context) {
153 if (const RegularGridAdjacency2D* adjacency = ::mmcfilters::detail::constructionAdjacency(tree)) {
154 return *adjacency;
155 }
156 if (const ComplementaryAdjacencies* adjacencies = ::mmcfilters::detail::complementaryAdjacencies(tree)) {
157 if (sameAdjacencyGraph(adjacencies->minAdjacency, adjacencies->maxAdjacency)) {
158 return adjacencies->minAdjacency;
159 }
160 throw std::invalid_argument(
161 std::string(context) +
162 " cannot infer one image graph from distinct minimum/maximum adjacencies; pass the intended adjacency explicitly.");
163 }
164 if (const TopographicConvention* convention = tree.topographicConvention();
165 convention != nullptr && std::holds_alternative<SelfDualSpanImmersion>(convention->immersion) && tree.hasGridDomain2D()) {
166 const GridDomain2D& domain = *tree.gridDomain2D();
167 return RegularGridAdjacency2D(domain.rows, domain.columns, 1.0);
168 }
169 throw std::invalid_argument(std::string(context) + " requires an attached adjacency relation; pass an explicit adjacency relation instead.");
170 }
171
172 public:
185 const MorphologicalTree& tree, const char* context = "HierarchySaliencyMap::requireProjectionAdjacency") {
186 return requireStoredAdjacency(tree, context);
187 }
188
201 [[nodiscard]] static std::vector<int> computeTopologicalLevels(const MorphologicalTree& tree) {
202 detail::requireCommittedRootedHierarchy(tree, "HierarchySaliencyMap::computeTopologicalLevels");
203
204 std::vector<int> levels(static_cast<std::size_t>(tree.numInternalNodeSlots()), 0);
205 for (NodeId nodeId : tree.postOrder()) {
206 int level = 0;
207 for (NodeId childId : tree.children(nodeId)) {
208 level = std::max(level, levels[static_cast<std::size_t>(childId)] + 1);
209 }
210 levels[static_cast<std::size_t>(nodeId)] = level;
211 }
212 return levels;
213 }
214
226 [[nodiscard]] static std::vector<int> computePartitionAppearanceLevels(const MorphologicalTree& tree) {
227 std::vector<int> levels = computeTopologicalLevels(tree);
228 for (NodeId nodeId : tree.aliveNodeIds()) {
229 ++levels[static_cast<std::size_t>(nodeId)];
230 }
231 return levels;
232 }
233
247 template <class NodeValue>
248 [[nodiscard]] static auto computeEdgeMap(const MorphologicalTree& tree, const RegularGridAdjacency2D& adjacency, NodeValue&& nodeValue)
250 using Value = std::decay_t<std::invoke_result_t<NodeValue, NodeId>>;
251 static_assert(!std::is_void_v<Value>, "HierarchySaliencyMap nodeValue must return a value.");
252
253 constexpr const char* context = "HierarchySaliencyMap::computeEdgeMap";
254 validateTreeAndAdjacency(tree, adjacency, context);
255
257 saliency.numRows = tree.numRows();
258 saliency.numColumns = tree.numColumns();
259 saliency.adjacencyRadius = adjacency.getRadius();
260
261 auto&& scorer = nodeValue;
262 const int numPixels = tree.numPixels();
263 for (NodeId source = 0; source < numPixels; ++source) {
264 const NodeId sourceSmallestNode = tree.smallestNode(source);
265 if (!tree.isAlive(sourceSmallestNode)) {
266 throw std::runtime_error("HierarchySaliencyMap::computeEdgeMap found a pixel without a live smallest node.");
267 }
268
269 for (int target : adjacency.getForwardNeighborIndices(source)) {
270 const NodeId targetSmallestNode = tree.smallestNode(target);
271 if (!tree.isAlive(targetSmallestNode)) {
272 throw std::runtime_error("HierarchySaliencyMap::computeEdgeMap found a neighbour pixel without a live smallest node.");
273 }
274
276 if (lca == InvalidNode || !tree.isAlive(lca)) {
277 throw std::runtime_error("HierarchySaliencyMap::computeEdgeMap could not find a live LCA for an adjacency edge.");
278 }
279
280 saliency.sources.push_back(source);
281 saliency.targets.push_back(target);
282 saliency.values.push_back(std::invoke(scorer, lca));
283 }
284 }
285
286 return saliency;
287 }
288
329 template <class Value>
331 computeSaliencyEdgeMap(const MorphologicalTree& tree, const RegularGridAdjacency2D& adjacency, std::span<const Value> valuation,
332 HierarchyValuationPolicy policy = HierarchyValuationPolicy::AllowLevelCollapse,
333 HierarchyLevelConvention levelConvention = HierarchyLevelConvention::EdgeSaliencyValue,
334 HierarchyConnectivityPolicy connectivityPolicy = HierarchyConnectivityPolicy::ValidateConnected) {
335 constexpr const char* context = "HierarchySaliencyMap::computeSaliencyEdgeMap";
336 HierarchySaliencyMapValidation::validateHierarchyValuation(tree, valuation, policy, HierarchyValuationRangePolicy::RequireNonNegative, context);
337 validateTreeAndAdjacency(tree, adjacency, context);
338 if (connectivityPolicy == HierarchyConnectivityPolicy::ValidateConnected) {
340 }
341 if (levelConvention == HierarchyLevelConvention::PartitionAppearanceLevel) {
342 for (NodeId nodeId : tree.aliveNodeIds()) {
343 const Value& level = valuation[static_cast<std::size_t>(nodeId)];
344 if (!(Value{} < level)) {
345 throw std::invalid_argument(std::string(context) + " requires positive partition-appearance levels.");
346 }
347 if constexpr (std::is_floating_point_v<Value>) {
348 if (std::floor(level) != level) {
349 throw std::invalid_argument(std::string(context) + " requires integer-valued partition-appearance levels.");
350 }
351 }
352 }
353 }
354
356 saliency.numRows = tree.numRows();
357 saliency.numColumns = tree.numColumns();
358 saliency.adjacencyRadius = adjacency.getRadius();
359
360 const int numPixels = tree.numPixels();
361 for (NodeId source = 0; source < numPixels; ++source) {
362 const NodeId sourceSmallestNode = tree.smallestNode(source);
363 if (!tree.isAlive(sourceSmallestNode)) {
364 throw std::runtime_error(std::string(context) + " found a pixel without a live smallest node.");
365 }
366
367 for (int target : adjacency.getForwardNeighborIndices(source)) {
368 const NodeId targetSmallestNode = tree.smallestNode(target);
369 if (!tree.isAlive(targetSmallestNode)) {
370 throw std::runtime_error(std::string(context) + " found a neighbour pixel without a live smallest node.");
371 }
372
376 if (lca == InvalidNode || !tree.isAlive(lca)) {
377 throw std::runtime_error(std::string(context) + " could not find a live LCA for an adjacency edge.");
378 }
379 edgeValue = valuation[static_cast<std::size_t>(lca)];
380 if (levelConvention == HierarchyLevelConvention::PartitionAppearanceLevel) {
381 edgeValue -= Value{1};
382 }
383 }
384
385 saliency.sources.push_back(source);
386 saliency.targets.push_back(target);
387 saliency.values.push_back(edgeValue);
388 }
389 }
390
391 return saliency;
392 }
393
408 template <class Value>
410 computeSaliencyEdgeMap(const MorphologicalTree& tree, std::span<const Value> valuation,
411 HierarchyValuationPolicy policy = HierarchyValuationPolicy::AllowLevelCollapse,
412 HierarchyLevelConvention levelConvention = HierarchyLevelConvention::EdgeSaliencyValue,
413 HierarchyConnectivityPolicy connectivityPolicy = HierarchyConnectivityPolicy::ValidateConnected) {
414 return computeSaliencyEdgeMap(tree, requireStoredAdjacency(tree, "HierarchySaliencyMap::computeSaliencyEdgeMap"), valuation, policy, levelConvention,
416 }
417
434 template <class Value>
437 HierarchyValuationPolicy policy = HierarchyValuationPolicy::AllowLevelCollapse,
438 HierarchyConnectivityPolicy connectivityPolicy = HierarchyConnectivityPolicy::ValidateConnected) {
439 constexpr const char* context = "HierarchySaliencyMap::computeCanonicalRankedSaliencyEdgeMap";
440 HierarchySaliencyMapValidation::validateHierarchyValuation(tree, valuation, policy, HierarchyValuationRangePolicy::AllowAnyFinite, context);
441 validateTreeAndAdjacency(tree, adjacency, context);
442 if (connectivityPolicy == HierarchyConnectivityPolicy::ValidateConnected) {
444 }
445
447 saliency.numRows = tree.numRows();
448 saliency.numColumns = tree.numColumns();
449 saliency.adjacencyRadius = adjacency.getRadius();
450
451 struct ProjectedLevel {
452 NodeId source;
453 NodeId target;
454 NodeId lca;
455 };
456 std::vector<ProjectedLevel> projected;
457 std::vector<Value> effectiveValues;
458 bool hasBaseEdge = false;
459 const int numPixels = tree.numPixels();
460 for (NodeId source = 0; source < numPixels; ++source) {
461 const NodeId sourceSmallestNode = tree.smallestNode(source);
462 for (int targetValue : adjacency.getForwardNeighborIndices(source)) {
463 const NodeId target = static_cast<NodeId>(targetValue);
464 const NodeId targetSmallestNode = tree.smallestNode(target);
465 NodeId lca = InvalidNode;
467 hasBaseEdge = true;
468 } else {
470 if (lca == InvalidNode || !tree.isAlive(lca)) {
471 throw std::runtime_error(std::string(context) + " could not find a live LCA for an adjacency edge.");
472 }
473 effectiveValues.push_back(valuation[static_cast<std::size_t>(lca)]);
474 }
475 projected.push_back(ProjectedLevel{source, target, lca});
476 }
477 }
478
479 std::sort(effectiveValues.begin(), effectiveValues.end());
480 effectiveValues.erase(std::unique(effectiveValues.begin(), effectiveValues.end()), effectiveValues.end());
481 saliency.sources.reserve(projected.size());
482 saliency.targets.reserve(projected.size());
483 saliency.values.reserve(projected.size());
484 const int transitionOffset = hasBaseEdge ? 1 : 0;
485 for (const ProjectedLevel& edge : projected) {
486 int rank = 0;
487 if (edge.lca != InvalidNode) {
488 const Value& value = valuation[static_cast<std::size_t>(edge.lca)];
489 rank = transitionOffset +
490 static_cast<int>(std::distance(effectiveValues.begin(), std::lower_bound(effectiveValues.begin(), effectiveValues.end(), value)));
491 }
492 saliency.sources.push_back(edge.source);
493 saliency.targets.push_back(edge.target);
494 saliency.values.push_back(rank);
495 }
496 return saliency;
497 }
498
509 template <class Value>
512 HierarchyValuationPolicy policy = HierarchyValuationPolicy::AllowLevelCollapse,
513 HierarchyConnectivityPolicy connectivityPolicy = HierarchyConnectivityPolicy::ValidateConnected) {
514 return computeCanonicalRankedSaliencyEdgeMap(tree, requireStoredAdjacency(tree, "HierarchySaliencyMap::computeCanonicalRankedSaliencyEdgeMap"),
516 }
517
526 template <class Value> [[nodiscard]] static EdgeSaliencyMap<int> rankEdgeSaliencyMap(const EdgeSaliencyMap<Value>& edgeMap) {
528 ranked.numRows = edgeMap.numRows;
529 ranked.numColumns = edgeMap.numColumns;
530 ranked.adjacencyRadius = edgeMap.adjacencyRadius;
531 ranked.sources = edgeMap.sources;
532 ranked.targets = edgeMap.targets;
533 std::vector<Value> uniqueValues = edgeMap.values;
534 std::sort(uniqueValues.begin(), uniqueValues.end());
535 uniqueValues.erase(std::unique(uniqueValues.begin(), uniqueValues.end()), uniqueValues.end());
536 ranked.values.reserve(edgeMap.values.size());
537 for (const Value& value : edgeMap.values) {
538 ranked.values.push_back(static_cast<int>(std::distance(uniqueValues.begin(), std::lower_bound(uniqueValues.begin(), uniqueValues.end(), value))));
539 }
540 return ranked;
541 }
542
550 template <class NodeValue>
553 return computeEdgeMap(tree, requireStoredAdjacency(tree, "HierarchySaliencyMap::computeEdgeMap"), std::forward<NodeValue>(nodeValue));
554 }
555
564 std::vector<int> levels = computeTopologicalLevels(tree);
565 return computeCanonicalRankedSaliencyEdgeMap(tree, adjacency, std::span<const int>(levels), HierarchyValuationPolicy::RequireStrictHierarchy);
566 }
567
575 return computeTopologicalLevelEdgeMap(tree, requireStoredAdjacency(tree, "HierarchySaliencyMap::computeTopologicalLevelEdgeMap"));
576 }
577
585 template <AltitudeValue T>
587 const RegularGridAdjacency2D& adjacency) {
589 return computeSaliencyEdgeMap(tree.topology(), adjacency, std::span<const double>(scores), HierarchyValuationPolicy::AllowLevelCollapse);
590 }
591
599 return computeNormalizedAltitudeEdgeMap(tree, requireStoredAdjacency(tree.topology(), "HierarchySaliencyMap::computeNormalizedAltitudeEdgeMap"));
600 }
601};
602
620 public:
629 static void validate(const MorphologicalTree& tree, const RegularGridAdjacency2D& adjacency) {
630 HierarchySaliencyMapValidation::validateHierarchyConnectivity(tree, adjacency, "ComponentTreePartitionHierarchyAdapter::validate");
631 }
632
642
655 template <class Value>
657 std::span<const Value> partitionAppearanceLevels,
658 HierarchyValuationPolicy policy = HierarchyValuationPolicy::AllowLevelCollapse) {
660 HierarchyLevelConvention::PartitionAppearanceLevel, HierarchyConnectivityPolicy::ValidateConnected);
661 }
662};
663
664} // namespace mmcfilters
constexpr NodeId InvalidNode
Sentinel value used to denote an invalid node identifier.
Definition Common.hpp:34
Explicit proper-part completion of a morphological component tree.
static void validate(const MorphologicalTree &tree, const RegularGridAdjacency2D &adjacency)
Validates the connected complete-partition interpretation.
static EdgeSaliencyMap< Value > computeSaliencyEdgeMap(const MorphologicalTree &tree, const RegularGridAdjacency2D &adjacency, std::span< const Value > partitionAppearanceLevels, HierarchyValuationPolicy policy=HierarchyValuationPolicy::AllowLevelCollapse)
Projects explicit partition-appearance levels with level(LCA)-1.
static std::vector< int > computePartitionAppearanceLevels(const MorphologicalTree &tree)
Returns the structural partition-appearance levels of the completion.
static std::vector< double > computeNormalizedScores(const MorphologicalTree &tree, std::span< const Value > valuation, HierarchyValuationPolicy policy=HierarchyValuationPolicy::AllowLevelCollapse, HierarchyValuationRangePolicy rangePolicy=HierarchyValuationRangePolicy::AllowAnyFinite)
Normalizes a compatible hierarchy valuation to [0, 1].
static void validateHierarchyConnectivity(const MorphologicalTree &tree, const RegularGridAdjacency2D &adjacency, const char *context="HierarchySaliencyMapValidation::validateHierarchyConnectivity")
Validates that every hierarchy support is connected in adjacency.
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.
Projects a morphological tree hierarchy onto an image adjacency graph.
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 std::vector< int > computeTopologicalLevels(const MorphologicalTree &tree)
Computes a dense topological level buffer indexed by internal NodeId.
static EdgeSaliencyMap< int > computeCanonicalRankedSaliencyEdgeMap(const MorphologicalTree &tree, std::span< const Value > valuation, HierarchyValuationPolicy policy=HierarchyValuationPolicy::AllowLevelCollapse, HierarchyConnectivityPolicy connectivityPolicy=HierarchyConnectivityPolicy::ValidateConnected)
Stored-adjacency overload of computeCanonicalRankedSaliencyEdgeMap.
static EdgeSaliencyMap< int > computeTopologicalLevelEdgeMap(const MorphologicalTree &tree)
Computes a topological-level map using one unambiguous stored graph.
static auto computeEdgeMap(const MorphologicalTree &tree, const RegularGridAdjacency2D &adjacency, NodeValue &&nodeValue) -> EdgeSaliencyMap< std::decay_t< std::invoke_result_t< NodeValue, NodeId > > >
Computes an edge saliency map using an explicit adjacency relation.
static EdgeSaliencyMap< double > computeNormalizedAltitudeEdgeMap(const ValuedMorphologicalTree< T > &tree, const RegularGridAdjacency2D &adjacency)
Computes a normalized-altitude edge saliency map with explicit adjacency.
static auto computeEdgeMap(const MorphologicalTree &tree, NodeValue &&nodeValue) -> EdgeSaliencyMap< std::decay_t< std::invoke_result_t< NodeValue, NodeId > > >
Computes an edge saliency map using one unambiguous stored graph.
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, std::span< const Value > valuation, HierarchyValuationPolicy policy=HierarchyValuationPolicy::AllowLevelCollapse, HierarchyLevelConvention levelConvention=HierarchyLevelConvention::EdgeSaliencyValue, HierarchyConnectivityPolicy connectivityPolicy=HierarchyConnectivityPolicy::ValidateConnected)
Computes a formal saliency map using one unambiguous stored graph.
static EdgeSaliencyMap< int > computeTopologicalLevelEdgeMap(const MorphologicalTree &tree, const RegularGridAdjacency2D &adjacency)
Computes a topological-level edge saliency map with explicit adjacency.
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 std::vector< int > computePartitionAppearanceLevels(const MorphologicalTree &tree)
Computes partition-appearance indexes for the proper-part completion.
static EdgeSaliencyMap< double > computeNormalizedAltitudeEdgeMap(const ValuedMorphologicalTree< T > &tree)
Computes a normalized-altitude map using one unambiguous stored graph.
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.
bool isAlive(NodeId nodeId) const
Tests whether a node slot currently represents a live node.
bool hasGridDomain2D() const noexcept
Tests whether pixel ids have an attached row/column layout.
int numPixels() const
Returns the cardinality of the pixel domain.
const TopographicConvention * topographicConvention() const noexcept
Returns the topographic convention, or nullptr.
AliveNodeRange aliveNodeIds() const
Returns a fail-fast range over all live node ids.
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.
ChildrenRange children(NodeId nodeId) const
Returns a fail-fast range over the direct children of nodeId.
int numColumns() const
Returns the number of columns in the regular 2D pixel domain.
const std::optional< GridDomain2D > & gridDomain2D() const noexcept
Returns the optional regular 2D pixel domain.
Immutable regular-grid 2D adjacency with allocation-free traversal.
double getRadius() const noexcept
Returns the configured or bounding Euclidean radius.
ForwardNeighborIndexRange getForwardNeighborIndices(int row, int column) const
Returns the directed positive half of the neighbourhood.
int getNumColumns() const noexcept
Returns the number of columns in the attached grid domain.
int getNumRows() const noexcept
Returns the number of rows in the attached grid domain.
Ordered pair of complementary minimum and maximum adjacencies.
Owning result for one computed scalar attribute layout and buffer.
std::vector< Real > & values() noexcept
Returns the mutable flat attribute buffer.
Edge-indexed saliency map induced by a morphological hierarchy.
int numRows
Number of rows in the proper-part grid.
bool empty() const noexcept
Returns whether the edge map has no values.
int numColumns
Number of columns in the proper-part grid.
double adjacencyRadius
Radius of the adjacency used to enumerate the edges.
std::vector< NodeId > sources
Source proper-part id of each undirected edge.
std::vector< Value > values
Saliency value parallel to sources and targets.
std::vector< NodeId > targets
Target proper-part id of each undirected edge.
std::size_t size() const noexcept
Returns the number of edge values.
Shape metadata optionally attached to the pixel domain.
int columns
Number of grid columns.
int rows
Number of grid rows.
Complete discrete convention retained by a tree-of-shapes result.