127 template <
class Value>
129 if constexpr (std::is_floating_point_v<Value>) {
130 if (!std::isfinite(value)) {
131 std::ostringstream
oss;
132 oss <<
context <<
" requires finite valuation values; node " << nodeId <<
" has value " << value <<
".";
133 throw std::invalid_argument(
oss.str());
136 if (
rangePolicy == HierarchyValuationRangePolicy::RequireNonNegative && value <
Value{}) {
137 std::ostringstream
oss;
138 oss <<
context <<
" requires non-negative valuation values; node " << nodeId <<
" has value " << value <<
".";
139 throw std::invalid_argument(
oss.str());
161 const char*
context =
"HierarchySaliencyMapValidation::validateHierarchyConnectivity") {
162 detail::requireCommittedRootedHierarchy(tree,
context);
163 const int rows = tree.
numRows();
166 throw std::invalid_argument(std::string(
context) +
" requires one graph vertex per 2D proper part and matching adjacency dimensions.");
169 validateHierarchyConnectivityEstablished(tree, adjacency,
context);
193 template <
class Value>
195 HierarchyValuationPolicy
policy = HierarchyValuationPolicy::AllowLevelCollapse,
196 HierarchyValuationRangePolicy
rangePolicy = HierarchyValuationRangePolicy::AllowAnyFinite,
197 const char*
context =
"HierarchySaliencyMapValidation::validateHierarchyValuation") {
198 detail::requireCommittedRootedHierarchy(tree,
context);
217 template <
class Value>
219 HierarchyValuationPolicy
policy = HierarchyValuationPolicy::AllowLevelCollapse) {
221 "HierarchySaliencyMapValidation::rankHierarchyValuation");
236 ranks[
static_cast<std::size_t
>(nodeId)] =
static_cast<int>(std::distance(
uniqueValues.begin(),
it));
263 template <
class Value>
266 HierarchyValuationPolicy
policy = HierarchyValuationPolicy::AllowLevelCollapse,
267 HierarchyValuationRangePolicy
rangePolicy = HierarchyValuationRangePolicy::AllowAnyFinite) {
268 using BareValue = std::remove_cv_t<Value>;
269 static_assert(std::is_arithmetic_v<BareValue> && !std::is_same_v<BareValue, bool>,
270 "HierarchySaliencyMapValidation::computeNormalizedScores requires a numeric non-bool valuation type.");
273 return normalizeHierarchyValuation(tree,
valuation);
290 detail::requireCommittedRootedHierarchy(topology,
"HierarchySaliencyMapValidation::computeNormalizedScores");
292 if (nodeAltitudeOrder == NodeAltitudeOrder::Unconstrained) {
293 throw std::invalid_argument(
"HierarchySaliencyMapValidation::computeNormalizedScores requires a globally monotone altitude order.");
302 orientedAltitude[
static_cast<std::size_t
>(nodeId)] = nodeAltitudeOrder == NodeAltitudeOrder::Increasing ? -altitude : altitude;
304 return normalizeHierarchyValuation(topology, std::span<const OrientedAltitude>(
orientedAltitude));
317 std::vector<NodeId> parent;
318 std::vector<int> size;
321 for (
NodeId id = 0;
id < count; ++id) {
322 parent[
static_cast<std::size_t
>(id)] =
id;
328 while (parent[
static_cast<std::size_t
>(root)] != root) {
329 root = parent[
static_cast<std::size_t
>(root)];
331 while (parent[
static_cast<std::size_t
>(
id)] != id) {
332 const NodeId next = parent[
static_cast<std::size_t
>(id)];
333 parent[
static_cast<std::size_t
>(id)] = root;
339 void unite(NodeId lhs, NodeId rhs) {
345 if (size[
static_cast<std::size_t
>(lhs)] < size[
static_cast<std::size_t
>(rhs)]) {
348 parent[
static_cast<std::size_t
>(rhs)] = lhs;
349 size[
static_cast<std::size_t
>(lhs)] += size[
static_cast<std::size_t
>(rhs)];
353 using DomainEdge = std::pair<NodeId, NodeId>;
354 std::vector<std::vector<DomainEdge>> edgesByLca(
static_cast<std::size_t
>(tree.
numInternalNodeSlots()));
355 for (NodeId source = 0; source < numPixels; ++source) {
356 const NodeId sourceSmallestNode = detail::TreeKernelAccess::smallestNodeMap(tree, source);
357 for (
int targetValue : detail::CommittedGridAccess::forwardNeighbors(adjacency, source)) {
359 const NodeId targetSmallestNode = detail::TreeKernelAccess::smallestNodeMap(tree, target);
360 const NodeId lca = sourceSmallestNode == targetSmallestNode ? sourceSmallestNode : detail::TreeKernelAccess::lowestCommonAncestor(tree, sourceSmallestNode, targetSmallestNode);
361 edgesByLca[
static_cast<std::size_t
>(lca)].emplace_back(source, target);
365 DomainDisjointSet components(numPixels);
366 std::vector<NodeId> supportRepresentative(
static_cast<std::size_t
>(tree.
numInternalNodeSlots()), InvalidNode);
367 for (NodeId nodeId : tree.postOrder()) {
368 for (
const DomainEdge& edge : edgesByLca[static_cast<std::size_t>(nodeId)]) {
369 components.unite(edge.first, edge.second);
373 auto acceptRepresentative = [&](
NodeId candidate) {
374 if (representative == InvalidNode) {
375 representative = candidate;
378 if (components.find(representative) != components.find(candidate)) {
379 std::ostringstream oss;
380 oss << context <<
" requires every hierarchy region to be connected in the projection graph; node " << nodeId
381 <<
" has a disconnected support.";
382 throw std::invalid_argument(oss.str());
386 for (PixelId pixel : detail::TreeKernelAccess::properParts(tree, nodeId)) {
387 acceptRepresentative(pixel);
389 for (NodeId childId : detail::TreeKernelAccess::children(tree, nodeId)) {
390 const NodeId childRepresentative = supportRepresentative[
static_cast<std::size_t
>(childId)];
391 acceptRepresentative(childRepresentative);
393 supportRepresentative[
static_cast<std::size_t
>(nodeId)] = components.find(representative);
406 template <
class Value>
407 static void validateHierarchyValuationEstablished(
const MorphologicalTree& tree, std::span<const Value> valuation,
408 HierarchyValuationPolicy policy, HierarchyValuationRangePolicy rangePolicy,
const char* context) {
409 if (valuation.size() !=
static_cast<std::size_t
>(tree.numInternalNodeSlots())) {
410 std::ostringstream oss;
411 oss << context <<
" requires one valuation value per dense internal node slot; expected " << tree.numInternalNodeSlots() <<
" values but got "
412 << valuation.size() <<
".";
413 throw std::invalid_argument(oss.str());
416 for (NodeId nodeId : tree.aliveNodeIds()) {
417 validateValuationValue(valuation[
static_cast<std::size_t
>(nodeId)], nodeId, context, rangePolicy);
420 for (NodeId parentId : tree.aliveNodeIds()) {
421 const Value& parentValue = valuation[
static_cast<std::size_t
>(parentId)];
422 for (NodeId childId : detail::TreeKernelAccess::children(tree, parentId)) {
423 const Value& childValue = valuation[
static_cast<std::size_t
>(childId)];
424 const bool validOrder = policy == HierarchyValuationPolicy::RequireStrictHierarchy ? childValue < parentValue : !(parentValue < childValue);
426 std::ostringstream oss;
427 oss << context <<
" requires "
428 << (policy == HierarchyValuationPolicy::RequireStrictHierarchy ?
"valuation(parent) > valuation(child)"
429 :
"valuation(parent) >= valuation(child)")
430 <<
"; parent node " << parentId <<
" has value " << parentValue <<
" and child node " << childId <<
" has value " << childValue <<
".";
431 throw std::invalid_argument(oss.str());
444 template <
class Value>
445 [[nodiscard]]
static std::vector<double> normalizeHierarchyValuation(
const MorphologicalTree& tree, std::span<const Value> valuation) {
446 using BareValue = std::remove_cv_t<Value>;
447 bool initialized =
false;
448 BareValue minValue{};
449 BareValue maxValue{};
450 for (NodeId nodeId : tree.aliveNodeIds()) {
451 const BareValue value = valuation[
static_cast<std::size_t
>(nodeId)];
457 minValue = std::min(minValue, value);
458 maxValue = std::max(maxValue, value);
462 std::vector<double> scores(
static_cast<std::size_t
>(tree.numInternalNodeSlots()), 0.0);
463 if (maxValue == minValue) {
467 for (NodeId nodeId : tree.aliveNodeIds()) {
468 const BareValue value = valuation[
static_cast<std::size_t
>(nodeId)];
469 long double normalized = 0.0L;
471 if constexpr (std::is_integral_v<BareValue>) {
472 using UnsignedValue = std::make_unsigned_t<BareValue>;
473 const UnsignedValue offset =
static_cast<UnsignedValue
>(value) -
static_cast<UnsignedValue
>(minValue);
474 const UnsignedValue range =
static_cast<UnsignedValue
>(maxValue) -
static_cast<UnsignedValue
>(minValue);
475 normalized =
static_cast<long double>(offset) /
static_cast<long double>(range);
477 const long double low =
static_cast<long double>(minValue);
478 const long double high =
static_cast<long double>(maxValue);
479 const long double current =
static_cast<long double>(value);
481 if (low < 0.0L && high > 0.0L) {
482 const long double scale = std::max(-low, high);
483 const long double scaledLow = low / scale;
484 const long double scaledHigh = high / scale;
485 normalized = (current / scale - scaledLow) / (scaledHigh - scaledLow);
487 normalized = (current - low) / (high - low);
491 scores[
static_cast<std::size_t
>(nodeId)] =
static_cast<double>(std::clamp(normalized, 0.0L, 1.0L));