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 {
132enum class HierarchyConnectivityPolicy {
160 template <
class Value>
162 if constexpr (std::is_floating_point_v<Value>) {
163 if (!std::isfinite(value)) {
164 std::ostringstream
oss;
165 oss <<
context <<
" requires finite valuation values; node " <<
nodeId <<
" has value " << value <<
".";
166 throw std::invalid_argument(
oss.str());
169 if (
rangePolicy == HierarchyValuationRangePolicy::RequireNonNegative && value <
Value{}) {
170 std::ostringstream
oss;
171 oss <<
context <<
" requires non-negative valuation values; node " <<
nodeId <<
" has value " << value <<
".";
172 throw std::invalid_argument(
oss.str());
194 const char*
context =
"HierarchySaliencyMapValidation::validateHierarchyConnectivity") {
195 detail::requireCommittedRootedHierarchy(tree,
context);
196 const int rows = tree.
numRows();
199 if (rows <= 0 || columns <= 0 || numPixels <= 0 || adjacency.
getNumRows() != rows || adjacency.
getNumColumns() != columns ||
200 static_cast<std::size_t
>(numPixels) !=
static_cast<std::size_t
>(rows) *
static_cast<std::size_t
>(columns)) {
201 throw std::invalid_argument(std::string(
context) +
" requires one graph vertex per 2D proper part and matching adjacency dimensions.");
205 std::vector<NodeId> parent;
206 std::vector<int> size;
210 parent[
static_cast<std::size_t
>(id)] =
id;
216 while (parent[
static_cast<std::size_t
>(root)] != root) {
217 root = parent[
static_cast<std::size_t
>(root)];
219 while (parent[
static_cast<std::size_t
>(
id)] != id) {
220 const NodeId next = parent[
static_cast<std::size_t
>(id)];
221 parent[
static_cast<std::size_t
>(id)] = root;
233 if (size[
static_cast<std::size_t
>(
lhs)] < size[
static_cast<std::size_t
>(
rhs)]) {
236 parent[
static_cast<std::size_t
>(
rhs)] =
lhs;
237 size[
static_cast<std::size_t
>(
lhs)] += size[
static_cast<std::size_t
>(
rhs)];
243 for (
NodeId source = 0; source < numPixels; ++source) {
246 throw std::invalid_argument(std::string(
context) +
" found a pixel without a live smallest node.");
252 throw std::invalid_argument(std::string(
context) +
" found a neighbour pixel without a live smallest node.");
256 throw std::invalid_argument(std::string(
context) +
" could not assign an adjacency edge to a live hierarchy node.");
276 std::ostringstream
oss;
277 oss <<
context <<
" requires every hierarchy region to be connected in the projection graph; node " <<
nodeId
278 <<
" has a disconnected support.";
279 throw std::invalid_argument(
oss.str());
289 throw std::invalid_argument(std::string(
context) +
" found a live child without proper-part support.");
294 throw std::invalid_argument(std::string(
context) +
" found a live node without proper-part support.");
321 template <
class Value>
323 HierarchyValuationPolicy
policy = HierarchyValuationPolicy::AllowLevelCollapse,
324 HierarchyValuationRangePolicy
rangePolicy = HierarchyValuationRangePolicy::AllowAnyFinite,
325 const char*
context =
"HierarchySaliencyMapValidation::validateHierarchyValuation") {
326 detail::requireCommittedRootedHierarchy(tree,
context);
328 std::ostringstream
oss;
331 throw std::invalid_argument(
oss.str());
344 std::ostringstream
oss;
346 << (
policy == HierarchyValuationPolicy::RequireStrictHierarchy ?
"valuation(parent) > valuation(child)"
347 :
"valuation(parent) >= valuation(child)")
349 throw std::invalid_argument(
oss.str());
370 template <
class Value>
372 HierarchyValuationPolicy
policy = HierarchyValuationPolicy::AllowLevelCollapse) {
374 "HierarchySaliencyMapValidation::rankHierarchyValuation");
416 template <
class Value>
419 HierarchyValuationPolicy
policy = HierarchyValuationPolicy::AllowLevelCollapse,
420 HierarchyValuationRangePolicy
rangePolicy = HierarchyValuationRangePolicy::AllowAnyFinite) {
421 using BareValue = std::remove_cv_t<Value>;
422 static_assert(std::is_arithmetic_v<BareValue> && !std::is_same_v<BareValue, bool>,
423 "HierarchySaliencyMapValidation::computeNormalizedScores requires a numeric non-bool valuation type.");
450 if constexpr (std::is_integral_v<BareValue>) {
454 normalized =
static_cast<long double>(offset) /
static_cast<long double>(
range);
456 const long double low =
static_cast<long double>(
minValue);
457 const long double high =
static_cast<long double>(
maxValue);
458 const long double current =
static_cast<long double>(value);
489 detail::requireCommittedRootedHierarchy(topology,
"HierarchySaliencyMapValidation::computeNormalizedScores");
491 if (nodeAltitudeOrder == NodeAltitudeOrder::Unconstrained) {
492 throw std::invalid_argument(
"HierarchySaliencyMapValidation::computeNormalizedScores requires a globally monotone altitude order.");
501 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 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.
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.