3#include "../utils/Altitude.hpp"
4#include "../utils/Contract.hpp"
5#include "../utils/Image.hpp"
6#include "MorphologicalTree.hpp"
7#include "detail/HigraHierarchyDetail.hpp"
8#include "detail/TreeKernelAccess.hpp"
24namespace detail::tree_altitude {
26template <
class Contribution>
inline void requireFiniteNodeContribution(Contribution contribution,
const char* context) {
27 if constexpr (std::is_floating_point_v<Contribution>) {
29 throw std::invalid_argument(std::string(context) +
" requires finite nodeContributions."));
34template <
class Contribution>
35 requires(std::is_arithmetic_v<Contribution> && !std::is_same_v<std::remove_cv_t<Contribution>,
bool>)
36[[nodiscard]] std::vector<Contribution> reconstructNodeContributionValuesEstablished(
const MorphologicalTree& tree,
37 std::span<const Contribution> nodeContributions) {
38 std::vector<Contribution> accumulated(
static_cast<std::size_t
>(tree.numInternalNodeSlots()), Contribution{});
39 const NodeId root = tree.root();
40 accumulated[
static_cast<std::size_t
>(root)] = nodeContributions[
static_cast<std::size_t
>(root)];
42 std::vector<NodeId> pending{root};
43 while (!pending.empty()) {
44 const NodeId nodeId = pending.back();
46 for (NodeId childId : TreeKernelAccess::children(tree, nodeId)) {
47 accumulated[
static_cast<std::size_t
>(childId)] =
48 accumulated[
static_cast<std::size_t
>(nodeId)] + nodeContributions[
static_cast<std::size_t
>(childId)];
49 pending.push_back(childId);
53 std::vector<Contribution> pixels(
static_cast<std::size_t
>(tree.numPixels()), Contribution{});
54 for (PixelId pixel = 0; pixel < tree.numPixels(); ++pixel) {
55 pixels[
static_cast<std::size_t
>(pixel)] = accumulated[
static_cast<std::size_t
>(TreeKernelAccess::smallestNodeMap(tree, pixel))];
60template <
class Contribution>
61 requires(std::is_arithmetic_v<Contribution> && !std::is_same_v<std::remove_cv_t<Contribution>,
bool>)
62[[nodiscard]] std::vector<Contribution> reconstructNodeContributionValues(
const MorphologicalTree& tree,
63 std::span<const Contribution> nodeContributions,
64 const char* context) {
65 tree.requireNotEditing(context);
67 nodeContributions.size() ==
static_cast<std::size_t
>(tree.numInternalNodeSlots()),
68 throw std::invalid_argument(std::string(context) +
" nodeContributions size must match the internal node slot count."));
69 if constexpr (std::is_floating_point_v<Contribution> && contract::validationsEnabled) {
70 for (Contribution contribution : nodeContributions) {
71 requireFiniteNodeContribution(contribution, context);
74 return reconstructNodeContributionValuesEstablished(tree, nodeContributions);
96 case NodeAltitudeOrder::Increasing:
98 case NodeAltitudeOrder::Decreasing:
100 case NodeAltitudeOrder::Unconstrained:
101 return "Unconstrained";
138 throw std::runtime_error(
"Altitude buffer size must match the dense internal-node domain."));
149 if constexpr (std::is_floating_point_v<T>) {
150 const long double level =
static_cast<long double>(altitude);
152 std::ostringstream
oss;
153 oss <<
context <<
" requires finite floating-point altitudes; value at index " << index <<
" is " <<
level <<
".";
154 throw std::invalid_argument(
oss.str());
166 if constexpr (std::is_floating_point_v<T> && contract::validationsEnabled) {
167 for (std::size_t index = 0; index < altitude.size(); ++index) {
180 if constexpr (std::is_floating_point_v<T> && contract::validationsEnabled) {
182 throw std::invalid_argument(
"Image altitude validation requires a non-null image.");
197 throw std::invalid_argument(
"Altitude access requires a valid internal NodeId."));
198 return altitude[
static_cast<std::size_t
>(nodeId)];
232 const long double level =
static_cast<long double>(altitude);
233 if constexpr (std::is_floating_point_v<T>) {
234 if (!std::isfinite(
level)) {
235 std::ostringstream
oss;
236 oss <<
context <<
" requires finite node altitudes in the uint8 domain [0, 255]; node " << nodeId <<
" has altitude " <<
level <<
".";
237 throw std::invalid_argument(
oss.str());
241 std::ostringstream
oss;
242 oss <<
context <<
" requires node altitudes in the uint8 domain [0, 255]; node " << nodeId <<
" has altitude " <<
level <<
".";
243 throw std::invalid_argument(
oss.str());
245 return static_cast<std::uint8_t
>(altitude);
274 template <AltitudeValue T>
276 const char*
context =
"TreeAltitudeAlgorithms::reconstructFromNodeAltitudes") {
301 template <
class Contribution>
302 requires(std::is_arithmetic_v<Contribution> && !std::is_same_v<std::remove_cv_t<Contribution>,
bool>)
305 const char*
context =
"TreeAltitudeAlgorithms::reconstructFromNodeContributions") {
326 template <AltitudeValue T>
329 const detail::ExportedHigraLayout
layout = detail::computeExportedHigraLayout(tree, altitude);
345 throw std::runtime_error(
"Each proper part must belong to one alive node when exporting a compact Higra hierarchy.");
347 parent[
static_cast<std::size_t
>(
leafIndex)] =
layout.nodeToHigra[
static_cast<std::size_t
>(smallestNodeId)];
354 parent[
static_cast<std::size_t
>(
newNodeId)] =
371 switch (nodeAltitudeOrder) {
372 case NodeAltitudeOrder::Increasing:
375 case NodeAltitudeOrder::Decreasing:
378 case NodeAltitudeOrder::Unconstrained:
383 if (tree.
isRoot(nodeId)) {
389 throw std::runtime_error(
"Monotonic validation requires every alive non-root node to have an alive parent.");
394 throw std::runtime_error(
"Hierarchy altitude buffer must be strictly increasing from parent to child.");
397 throw std::runtime_error(
"Hierarchy altitude buffer must be strictly decreasing from parent to child.");
int NodeId
Node identifier type used throughout the project.
constexpr NodeId InvalidNode
Sentinel value used to denote an invalid node identifier.
#define MMCFILTERS_CONTRACT_REQUIRE(condition,...)
Evaluates a caller precondition and its failure action only in checked builds.
NodeAltitudeOrder
Global ordering constraint of node altitudes along parent-child arcs.
static Ptr create(int rows, int columns)
Creates an owned image with uninitialised pixel values.
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.
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.
bool isRoot(NodeId nodeId) const
Tests whether nodeId is the current root.
AliveNodeRange aliveNodeIds() const
Returns a fail-fast range over all live node ids.
NodeId smallestNode(PixelId pixel) const
Returns the smallest node containing pixel.
void requireNotEditing(const char *context) const
Rejects operations that require a committed connected topology.
int numColumns() const
Returns the number of columns in the regular 2D pixel domain.
NodeId parent(NodeId nodeId) const
Returns the direct parent of nodeId.
Pure operations over a topology and an explicit altitude buffer.
static void validateNodeAltitudeBufferShape(const MorphologicalTree &tree, std::span< const T > altitude)
Validates that an altitude buffer covers the dense internal-node domain.
static void validateUInt8AltitudeDomain(const MorphologicalTree &tree, std::span< const T > altitude, const char *context)
Validates all live node altitudes before materialising an ImageUInt8.
static std::uint8_t requireUInt8AltitudeValue(T altitude, NodeId nodeId, const char *context)
Converts one altitude value to uint8_t, rejecting values outside the output domain.
static void validateMonotoneNodeAltitudes(const MorphologicalTree &tree, std::span< const T > altitude)
Validates the hierarchy's declared global altitude order.
static void validateFiniteImageAltitudes(const ImagePtr< T > &image, const char *context)
Rejects non-finite floating-point pixels before using an image as altitude source.
static ImagePtr< Contribution > reconstructFromNodeContributions(const MorphologicalTree &tree, std::span< const Contribution > nodeContributions, const char *context="TreeAltitudeAlgorithms::reconstructFromNodeContributions")
Reconstructs an image by summing node contributions on every root-to-node branch.
static T nodeAltitude(std::span< const T > altitude, NodeId nodeId)
Reads one node altitude from an explicit altitude buffer.
static void validateFiniteAltitudeValue(T altitude, std::size_t index, const char *context)
Rejects non-finite floating-point altitudes while compiling to a no-op for integral types.
static bool hasGlobalMonotoneAltitudeOrder(const MorphologicalTree &tree) noexcept
Tests whether the topology declares a globally monotone altitude order.
static void validateFiniteAltitudeValues(std::span< const T > altitude, const char *context)
Rejects non-finite floating-point altitudes in a contiguous input range.
static void requireGlobalMonotoneAltitudeOrder(const MorphologicalTree &tree, const char *context)
Requires a declared globally monotone altitude order.
static const char * nodeAltitudeOrderName(NodeAltitudeOrder order) noexcept
Returns the name of a declared node-altitude order.
static AltitudeDifference< T > nodeResidue(const MorphologicalTree &tree, std::span< const T > altitude, NodeId nodeId)
Computes the altitude difference between one node and its parent.
static std::pair< std::vector< NodeId >, std::vector< T > > exportHigraHierarchy(const MorphologicalTree &tree, std::span< const T > altitude)
Exports a live rooted topology and explicit altitudes to a compact parent/altitude representation.
static ImagePtr< T > reconstructFromNodeAltitudes(const MorphologicalTree &tree, std::span< const T > altitude, const char *context="TreeAltitudeAlgorithms::reconstructFromNodeAltitudes")
Reconstructs a typed image from topology storage and explicit node altitudes.
Owning result for one computed scalar attribute layout and buffer.