3#include "../../utils/Altitude.hpp"
4#include "../../utils/Common.hpp"
5#include "../../utils/RegularGridAdjacency2D.hpp"
6#include "../MorphologicalTree.hpp"
7#include "../ValuedMorphologicalTree.hpp"
37inline void requireCommittedRootedHierarchy(
const MorphologicalTree& tree,
const char* context) {
38 if (tree.isEditing()) {
39 throw std::invalid_argument(std::string(context) +
" requires a committed tree; an edit session is still open.");
41 if (tree.root() == InvalidNode || !tree.isAlive(tree.root())) {
42 throw std::invalid_argument(std::string(context) +
" requires a non-empty connected rooted tree.");
44 if (tree.parent(tree.root()) != tree.root()) {
45 throw std::invalid_argument(std::string(context) +
" requires the root to point to itself.");
48 const std::size_t slotCount =
static_cast<std::size_t
>(tree.numInternalNodeSlots());
49 std::vector<std::uint8_t> visited(slotCount, 0);
50 std::vector<NodeId> stack{tree.root()};
51 visited[
static_cast<std::size_t
>(tree.root())] = 1;
52 std::size_t visitedCount = 0;
53 std::size_t traversedEdges = 0;
55 while (!stack.empty()) {
56 const NodeId nodeId = stack.back();
60 for (NodeId childId : tree.children(nodeId)) {
62 if (traversedEdges >= slotCount || !tree.isAlive(childId) || tree.parent(childId) != nodeId) {
63 throw std::invalid_argument(std::string(context) +
" requires consistent parent-child relations.");
65 const std::size_t childIndex =
static_cast<std::size_t
>(childId);
66 if (visited[childIndex] != 0) {
67 throw std::invalid_argument(std::string(context) +
" requires an acyclic rooted tree.");
69 visited[childIndex] = 1;
70 stack.push_back(childId);
74 std::size_t aliveCount = 0;
75 for (NodeId nodeId : tree.aliveNodeIds()) {
77 if (visited[
static_cast<std::size_t
>(nodeId)] == 0) {
78 throw std::invalid_argument(std::string(context) +
" requires every live node to be connected to the root.");
81 if (visitedCount != aliveCount) {
82 throw std::invalid_argument(std::string(context) +
" requires one connected rooted tree.");
104enum class HierarchyValuationPolicy {
106 RequireStrictHierarchy,
118enum class HierarchyValuationRangePolicy {
136enum class HierarchyConnectivityPolicy {
164 template <
class Value>
166 if constexpr (std::is_floating_point_v<Value>) {
167 if (!std::isfinite(value)) {
168 std::ostringstream
oss;
169 oss <<
context <<
" requires finite valuation values; node " <<
nodeId <<
" has value " << value <<
".";
170 throw std::invalid_argument(
oss.str());
173 if (
rangePolicy == HierarchyValuationRangePolicy::RequireNonNegative && value <
Value{}) {
174 std::ostringstream
oss;
175 oss <<
context <<
" requires non-negative valuation values; node " <<
nodeId <<
" has value " << value <<
".";
176 throw std::invalid_argument(
oss.str());
198 const char*
context =
"HierarchySaliencyMapValidation::validateHierarchyConnectivity") {
199 detail::requireCommittedRootedHierarchy(tree,
context);
200 const int rows = tree.
numRows();
203 if (rows <= 0 || columns <= 0 || numPixels <= 0 || adjacency.
getNumRows() != rows || adjacency.
getNumColumns() != columns ||
204 static_cast<std::size_t
>(numPixels) !=
static_cast<std::size_t
>(rows) *
static_cast<std::size_t
>(columns)) {
205 throw std::invalid_argument(std::string(
context) +
" requires one graph vertex per 2D proper part and matching adjacency dimensions.");
209 std::vector<NodeId> parent;
210 std::vector<int> size;
214 parent[
static_cast<std::size_t
>(id)] =
id;
220 while (parent[
static_cast<std::size_t
>(root)] != root) {
221 root = parent[
static_cast<std::size_t
>(root)];
223 while (parent[
static_cast<std::size_t
>(
id)] != id) {
224 const NodeId next = parent[
static_cast<std::size_t
>(id)];
225 parent[
static_cast<std::size_t
>(id)] = root;
237 if (size[
static_cast<std::size_t
>(
lhs)] < size[
static_cast<std::size_t
>(
rhs)]) {
240 parent[
static_cast<std::size_t
>(
rhs)] =
lhs;
241 size[
static_cast<std::size_t
>(
lhs)] += size[
static_cast<std::size_t
>(
rhs)];
247 for (
NodeId source = 0; source < numPixels; ++source) {
250 throw std::invalid_argument(std::string(
context) +
" found a pixel without a live smallest node.");
256 throw std::invalid_argument(std::string(
context) +
" found a neighbour pixel without a live smallest node.");
260 throw std::invalid_argument(std::string(
context) +
" could not assign an adjacency edge to a live hierarchy node.");
280 std::ostringstream
oss;
281 oss <<
context <<
" requires every hierarchy region to be connected in the projection graph; node " <<
nodeId
282 <<
" has a disconnected support.";
283 throw std::invalid_argument(
oss.str());
293 throw std::invalid_argument(std::string(
context) +
" found a live child without proper-part support.");
298 throw std::invalid_argument(std::string(
context) +
" found a live node without proper-part support.");
325 template <
class Value>
327 HierarchyValuationPolicy
policy = HierarchyValuationPolicy::AllowLevelCollapse,
328 HierarchyValuationRangePolicy
rangePolicy = HierarchyValuationRangePolicy::AllowAnyFinite,
329 const char*
context =
"HierarchySaliencyMapValidation::validateHierarchyValuation") {
330 detail::requireCommittedRootedHierarchy(tree,
context);
332 std::ostringstream
oss;
335 throw std::invalid_argument(
oss.str());
348 std::ostringstream
oss;
350 << (
policy == HierarchyValuationPolicy::RequireStrictHierarchy ?
"valuation(parent) > valuation(child)"
351 :
"valuation(parent) >= valuation(child)")
353 throw std::invalid_argument(
oss.str());
374 template <
class Value>
376 HierarchyValuationPolicy
policy = HierarchyValuationPolicy::AllowLevelCollapse) {
378 "HierarchySaliencyMapValidation::rankHierarchyValuation");
420 template <
class Value>
423 HierarchyValuationPolicy
policy = HierarchyValuationPolicy::AllowLevelCollapse,
424 HierarchyValuationRangePolicy
rangePolicy = HierarchyValuationRangePolicy::AllowAnyFinite) {
425 using BareValue = std::remove_cv_t<Value>;
426 static_assert(std::is_arithmetic_v<BareValue> && !std::is_same_v<BareValue, bool>,
427 "HierarchySaliencyMapValidation::computeNormalizedScores requires a numeric non-bool valuation type.");
454 if constexpr (std::is_integral_v<BareValue>) {
458 normalized =
static_cast<long double>(offset) /
static_cast<long double>(
range);
460 const long double low =
static_cast<long double>(
minValue);
461 const long double high =
static_cast<long double>(
maxValue);
462 const long double current =
static_cast<long double>(value);
493 detail::requireCommittedRootedHierarchy(topology,
"HierarchySaliencyMapValidation::computeNormalizedScores");
495 if (nodeAltitudeOrder == NodeAltitudeOrder::Unconstrained) {
496 throw std::invalid_argument(
"HierarchySaliencyMapValidation::computeNormalizedScores requires a globally monotone altitude order.");
505 orientedAltitude[
static_cast<std::size_t
>(
nodeId)] = nodeAltitudeOrder == NodeAltitudeOrder::Increasing ? -altitude : altitude;
int NodeId
Node identifier type used throughout the project.
constexpr NodeId InvalidNode
Sentinel value used to denote an invalid node identifier.
NodeAltitudeOrder
Global ordering constraint of node altitudes along parent-child arcs.
Validates and transforms hierarchy valuations used by saliency maps.
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.
static std::vector< int > rankHierarchyValuation(const MorphologicalTree &tree, std::span< const Value > valuation, HierarchyValuationPolicy policy=HierarchyValuationPolicy::AllowLevelCollapse)
Converts a compatible valuation to dense non-negative integer levels.
static std::vector< double > computeNormalizedScores(const ValuedMorphologicalTree< T > &tree)
Computes a dense normalized altitude score buffer in [0, 1].
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.
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.
NodeAltitudeOrder nodeAltitudeOrder() const noexcept
Returns the global parent-to-child altitude ordering constraint.
int numPixels() const
Returns the cardinality of the pixel domain.
AliveNodeRange aliveNodeIds() const
Returns a fail-fast range over all live node ids.
NodeId smallestNode(PixelId pixel) const
Returns the 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.
Immutable regular-grid 2D adjacency with allocation-free traversal.
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.
Owning result for one computed scalar attribute layout and buffer.
std::vector< Real > second
Flat per-node attribute buffer indexed through first.
AttributeNames first
Layout used to interpret second; kept public for tuple-like access.