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 "../ValuedMorphologicalTree.hpp"
70enum class HierarchyLevelConvention {
72 PartitionAppearanceLevel,
110 detail::requireCommittedRootedHierarchy(tree,
context);
112 throw std::invalid_argument(std::string(
context) +
" requires a non-empty image/pixel domain.");
115 std::ostringstream
oss;
117 <<
" for a tree domain of " << tree.
numRows() <<
"x" << tree.
numColumns() <<
".";
118 throw std::invalid_argument(
oss.str());
133 if (
lhs.getNumRows() !=
rhs.getNumRows() ||
lhs.getNumColumns() !=
rhs.getNumColumns() ||
lhs.getSize() !=
rhs.getSize()) {
136 for (
int index = 0; index <
lhs.getSize(); ++index) {
137 if (
lhs.getOffsetRow(index) !=
rhs.getOffsetRow(index) ||
lhs.getOffsetColumn(index) !=
rhs.getOffsetColumn(index)) {
159 throw std::invalid_argument(
161 " cannot infer one image graph from distinct minimum/maximum adjacencies; pass the intended adjacency explicitly.");
164 convention !=
nullptr && std::holds_alternative<SelfDualSpanImmersion>(convention->immersion) && tree.
hasGridDomain2D()) {
168 throw std::invalid_argument(std::string(
context) +
" requires an attached adjacency relation; pass an explicit adjacency relation instead.");
185 return requireStoredAdjacency(tree,
context);
201 detail::requireCommittedRootedHierarchy(tree,
"HierarchySaliencyMap::computeTopologicalLevels");
202 return computeTopologicalLevelsEstablished(tree);
219 ++
levels[
static_cast<std::size_t
>(nodeId)];
237 template <
class NodeValue>
240 using Value = std::decay_t<std::invoke_result_t<NodeValue, NodeId>>;
241 static_assert(!std::is_void_v<Value>,
"HierarchySaliencyMap nodeValue must return a value.");
243 constexpr const char*
context =
"HierarchySaliencyMap::computeEdgeMap";
244 validateTreeAndAdjacency(tree, adjacency,
context);
254 for (
NodeId source = 0; source < numPixels; ++source) {
257 for (
int target : detail::CommittedGridAccess::forwardNeighbors(adjacency, source)) {
312 template <
class Value>
315 HierarchyValuationPolicy
policy = HierarchyValuationPolicy::AllowLevelCollapse,
316 HierarchyLevelConvention
levelConvention = HierarchyLevelConvention::EdgeSaliencyValue,
317 HierarchyConnectivityPolicy
connectivityPolicy = HierarchyConnectivityPolicy::ValidateConnected) {
318 constexpr const char*
context =
"HierarchySaliencyMap::computeSaliencyEdgeMap";
319 validateTreeAndAdjacency(tree, adjacency,
context);
320 HierarchySaliencyMapValidation::validateHierarchyValuationEstablished(tree,
valuation,
policy,
321 HierarchyValuationRangePolicy::RequireNonNegative,
context);
323 HierarchySaliencyMapValidation::validateHierarchyConnectivityEstablished(tree, adjacency,
context);
325 if (
levelConvention == HierarchyLevelConvention::PartitionAppearanceLevel) {
329 throw std::invalid_argument(std::string(
context) +
" requires positive partition-appearance levels.");
331 if constexpr (std::is_floating_point_v<Value>) {
333 throw std::invalid_argument(std::string(
context) +
" requires integer-valued partition-appearance levels.");
356 template <
class Value>
359 HierarchyValuationPolicy
policy = HierarchyValuationPolicy::AllowLevelCollapse,
360 HierarchyLevelConvention
levelConvention = HierarchyLevelConvention::EdgeSaliencyValue,
361 HierarchyConnectivityPolicy
connectivityPolicy = HierarchyConnectivityPolicy::ValidateConnected) {
382 template <
class Value>
385 HierarchyValuationPolicy
policy = HierarchyValuationPolicy::AllowLevelCollapse,
386 HierarchyConnectivityPolicy
connectivityPolicy = HierarchyConnectivityPolicy::ValidateConnected) {
387 constexpr const char*
context =
"HierarchySaliencyMap::computeCanonicalRankedSaliencyEdgeMap";
388 validateTreeAndAdjacency(tree, adjacency,
context);
389 HierarchySaliencyMapValidation::validateHierarchyValuationEstablished(tree,
valuation,
policy,
390 HierarchyValuationRangePolicy::AllowAnyFinite,
context);
392 HierarchySaliencyMapValidation::validateHierarchyConnectivityEstablished(tree, adjacency,
context);
395 return computeCanonicalRankedSaliencyEdgeMapEstablished(tree, adjacency,
valuation);
408 template <
class Value>
411 HierarchyValuationPolicy
policy = HierarchyValuationPolicy::AllowLevelCollapse,
412 HierarchyConnectivityPolicy
connectivityPolicy = HierarchyConnectivityPolicy::ValidateConnected) {
427 ranked.numRows = edgeMap.numRows;
428 ranked.numColumns = edgeMap.numColumns;
429 ranked.adjacencyRadius = edgeMap.adjacencyRadius;
430 ranked.sources = edgeMap.sources;
431 ranked.targets = edgeMap.targets;
449 template <
class NodeValue>
452 return computeEdgeMap(tree, requireStoredAdjacency(tree,
"HierarchySaliencyMap::computeEdgeMap"), std::forward<NodeValue>(
nodeValue));
463 constexpr const char*
context =
"HierarchySaliencyMap::computeTopologicalLevelEdgeMap";
464 validateTreeAndAdjacency(tree, adjacency,
context);
465 HierarchySaliencyMapValidation::validateHierarchyConnectivityEstablished(tree, adjacency,
context);
466 std::vector<int>
levels = computeTopologicalLevelsEstablished(tree);
467 return computeCanonicalRankedSaliencyEdgeMapEstablished(tree, adjacency, std::span<const int>(
levels));
487 template <AltitudeValue T>
490 constexpr const char*
context =
"HierarchySaliencyMap::computeNormalizedAltitudeEdgeMap";
491 validateTreeAndAdjacency(tree.topology(), adjacency,
context);
493 HierarchySaliencyMapValidation::validateHierarchyConnectivityEstablished(tree.topology(), adjacency,
context);
494 return computeSaliencyEdgeMapEstablished(tree.topology(), adjacency, std::span<const double>(
scores), HierarchyLevelConvention::EdgeSaliencyValue);
516 for (
NodeId nodeId : tree.postOrder()) {
518 for (
NodeId childId : detail::TreeKernelAccess::children(tree, nodeId)) {
521 levels[
static_cast<std::size_t
>(nodeId)] = level;
535 template <
class Value>
536 [[nodiscard]]
static EdgeSaliencyMap<Value> computeSaliencyEdgeMapEstablished(
const MorphologicalTree& tree,
537 const RegularGridAdjacency2D& adjacency, std::span<const Value> valuation, HierarchyLevelConvention levelConvention) {
538 EdgeSaliencyMap<Value> saliency;
539 saliency.numRows = tree.numRows();
540 saliency.numColumns = tree.numColumns();
541 saliency.adjacencyRadius = adjacency.getRadius();
543 const int numPixels = tree.numPixels();
544 for (NodeId source = 0; source < numPixels; ++source) {
545 const NodeId sourceSmallestNode = detail::TreeKernelAccess::smallestNodeMap(tree, source);
547 for (
int target : detail::CommittedGridAccess::forwardNeighbors(adjacency, source)) {
548 const NodeId targetSmallestNode = detail::TreeKernelAccess::smallestNodeMap(tree, target);
551 if (sourceSmallestNode != targetSmallestNode) {
552 const NodeId lca = detail::TreeKernelAccess::lowestCommonAncestor(tree, sourceSmallestNode, targetSmallestNode);
553 edgeValue = valuation[
static_cast<std::size_t
>(lca)];
554 if (levelConvention == HierarchyLevelConvention::PartitionAppearanceLevel) {
555 edgeValue -= Value{1};
559 saliency.sources.push_back(source);
560 saliency.targets.push_back(target);
561 saliency.values.push_back(edgeValue);
576 template <
class Value>
577 [[nodiscard]]
static EdgeSaliencyMap<int> computeCanonicalRankedSaliencyEdgeMapEstablished(
const MorphologicalTree& tree,
578 const RegularGridAdjacency2D& adjacency, std::span<const Value> valuation) {
579 EdgeSaliencyMap<int> saliency;
580 saliency.numRows = tree.numRows();
581 saliency.numColumns = tree.numColumns();
582 saliency.adjacencyRadius = adjacency.getRadius();
584 struct ProjectedLevel {
589 std::vector<ProjectedLevel> projected;
590 std::vector<Value> effectiveValues;
591 bool hasBaseEdge =
false;
592 const int numPixels = tree.numPixels();
593 for (NodeId source = 0; source < numPixels; ++source) {
594 const NodeId sourceSmallestNode = detail::TreeKernelAccess::smallestNodeMap(tree, source);
595 for (
int targetValue : detail::CommittedGridAccess::forwardNeighbors(adjacency, source)) {
597 const NodeId targetSmallestNode = detail::TreeKernelAccess::smallestNodeMap(tree, target);
599 if (sourceSmallestNode == targetSmallestNode) {
602 lca = detail::TreeKernelAccess::lowestCommonAncestor(tree, sourceSmallestNode, targetSmallestNode);
603 effectiveValues.push_back(valuation[
static_cast<std::size_t
>(lca)]);
605 projected.push_back(ProjectedLevel{source, target, lca});
609 std::sort(effectiveValues.begin(), effectiveValues.end());
610 effectiveValues.erase(std::unique(effectiveValues.begin(), effectiveValues.end()), effectiveValues.end());
611 saliency.sources.reserve(projected.size());
612 saliency.targets.reserve(projected.size());
613 saliency.values.reserve(projected.size());
614 const int transitionOffset = hasBaseEdge ? 1 : 0;
615 for (
const ProjectedLevel& edge : projected) {
617 if (edge.lca != InvalidNode) {
618 const Value& value = valuation[
static_cast<std::size_t
>(edge.lca)];
619 rank = transitionOffset +
620 static_cast<int>(std::distance(effectiveValues.begin(), std::lower_bound(effectiveValues.begin(), effectiveValues.end(), value)));
622 saliency.sources.push_back(edge.source);
623 saliency.targets.push_back(edge.target);
624 saliency.values.push_back(rank);
682 template <
class Value>
685 HierarchyValuationPolicy
policy = HierarchyValuationPolicy::AllowLevelCollapse) {
687 HierarchyLevelConvention::PartitionAppearanceLevel, HierarchyConnectivityPolicy::ValidateConnected);
int NodeId
Node identifier type used throughout the project.
constexpr NodeId InvalidNode
Sentinel value used to denote an invalid node identifier.
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.
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.
const ComplementaryAdjacencies * complementaryAdjacencies() const noexcept
Returns the complementary adjacencies explicitly retained by a topographic immersion.
void requireMutationVersion(std::size_t expectedVersion, const char *context) const
Rejects stale read-only views that captured an older mutation version.
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.
const RegularGridAdjacency2D * constructionAdjacency() const noexcept
Returns the adjacency retained by a shared construction context.
AliveNodeRange aliveNodeIds() const
Returns a fail-fast range over all live node ids.
std::size_t getMutationVersion() const noexcept
Returns the monotonic mutation counter used by read-only views.
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.
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 image grid.
bool empty() const noexcept
Returns whether the edge map has no values.
int numColumns
Number of columns in the image grid.
double adjacencyRadius
Radius of the adjacency used to enumerate the edges.
std::vector< NodeId > sources
Source pixel identifier of each undirected edge.
std::vector< Value > values
Saliency value parallel to sources and targets.
std::vector< NodeId > targets
Target pixel identifier 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.