MorphologicalAttributeFilters
Public API documentation
Loading...
Searching...
No Matches
TreeAltitudeAlgorithms.hpp
1#pragma once
2
3#include "../utils/Altitude.hpp"
4#include "MorphologicalTree.hpp"
5#include "detail/HigraExportLayoutDetail.hpp"
6
7#include <cmath>
8#include <cstddef>
9#include <cstdint>
10#include <span>
11#include <sstream>
12#include <stdexcept>
13#include <type_traits>
14#include <utility>
15#include <vector>
16
17namespace mmcfilters {
18
27public:
31 template<AltitudeValue T>
32 static void validateAltitudeBufferShape(const MorphologicalTree& tree, std::span<const T> altitude) {
33 if (altitude.size() != static_cast<std::size_t>(tree.getNumInternalNodeSlots())) {
34 throw std::runtime_error("Altitude buffer size must match the dense internal-node domain.");
35 }
36 }
37
41 template<AltitudeValue T>
42 static void validateFiniteAltitudeValue(T altitude, std::size_t index, const char* context) {
43 if constexpr (std::is_floating_point_v<T>) {
44 const long double level = static_cast<long double>(altitude);
45 if (!std::isfinite(level)) {
46 std::ostringstream oss;
47 oss << context << " requires finite floating-point altitudes; value at index "
48 << index << " is " << level << ".";
49 throw std::invalid_argument(oss.str());
50 }
51 }
52 }
53
57 template<AltitudeValue T>
58 static void validateFiniteAltitudeValues(std::span<const T> altitude, const char* context) {
59 if constexpr (std::is_floating_point_v<T>) {
60 for (std::size_t index = 0; index < altitude.size(); ++index) {
61 validateFiniteAltitudeValue(altitude[index], index, context);
62 }
63 }
64 }
65
69 template<AltitudeValue T>
70 static void validateFiniteImageAltitudes(const ImagePtr<T>& image, const char* context) {
71 if constexpr (std::is_floating_point_v<T>) {
72 if (!image) {
73 throw std::invalid_argument("Image altitude validation requires a non-null image.");
74 }
76 std::span<const T>(image->rawData(), static_cast<std::size_t>(image->getSize())),
77 context);
78 }
79 }
80
84 template<AltitudeValue T>
85 [[nodiscard]] static T getAltitude(std::span<const T> altitude, NodeId nodeId) {
86 if (nodeId < 0 || static_cast<std::size_t>(nodeId) >= altitude.size()) {
87 throw std::invalid_argument("Altitude access requires a valid internal NodeId.");
88 }
89 return altitude[static_cast<std::size_t>(nodeId)];
90 }
91
95 template<AltitudeValue T>
96 [[nodiscard]] static AltitudeDiff<T> getNodeResidue(const MorphologicalTree& tree, std::span<const T> altitude, NodeId nodeId) {
97 validateAltitudeBufferShape(tree, altitude);
98 if (!tree.isAlive(nodeId)) {
99 throw std::invalid_argument("Node residue requires a live internal NodeId.");
100 }
103 return static_cast<AltitudeDiff<T>>(getAltitude(altitude, nodeId));
104 }
105 return static_cast<AltitudeDiff<T>>(getAltitude(altitude, nodeId)) -
106 static_cast<AltitudeDiff<T>>(getAltitude(altitude, parentNodeId));
107 }
108
112 template<AltitudeValue T>
113 [[nodiscard]] static std::uint8_t requireUInt8AltitudeValue(T altitude, NodeId nodeId, const char* context) {
114 const long double level = static_cast<long double>(altitude);
115 if constexpr (std::is_floating_point_v<T>) {
116 if (!std::isfinite(level)) {
117 std::ostringstream oss;
118 oss << context << " requires finite node altitudes in the uint8 domain [0, 255]; node "
119 << nodeId << " has altitude " << level << ".";
120 throw std::invalid_argument(oss.str());
121 }
122 }
123 if (level < 0.0L || level > 255.0L) {
124 std::ostringstream oss;
125 oss << context << " requires node altitudes in the uint8 domain [0, 255]; node "
126 << nodeId << " has altitude " << level << ".";
127 throw std::invalid_argument(oss.str());
128 }
129 return static_cast<std::uint8_t>(altitude);
130 }
131
135 template<AltitudeValue T>
136 static void validateUInt8AltitudeDomain(const MorphologicalTree& tree, std::span<const T> altitude, const char* context) {
137 validateAltitudeBufferShape(tree, altitude);
138 for (NodeId nodeId : tree.getAliveNodeIds()) {
140 }
141 }
142
150 template<AltitudeValue T>
151 [[nodiscard]] static ImagePtr<T> reconstructImage(const MorphologicalTree& tree, std::span<const T> altitude, const char* context = "TreeAltitudeAlgorithms::reconstructImage") {
152 (void)context;
154 validateAltitudeBufferShape(tree, altitude);
156 auto imgBuffer = image->rawData();
157 for (int pixelId = 0; pixelId < tree.getNumTotalProperParts(); ++pixelId) {
159 imgBuffer[static_cast<std::size_t>(pixelId)] = getAltitude(altitude, nodeId);
160 }
161 return image;
162 }
163
173 template<AltitudeValue T>
174 [[nodiscard]] static std::pair<std::vector<NodeId>, std::vector<T>> exportHigraHierarchy(const MorphologicalTree& tree, std::span<const T> altitude) {
175 tree.requireNotEditing("TreeAltitudeAlgorithms::exportHigraHierarchy");
176 const detail::ExportedHigraLayout layout = detail::computeExportedHigraLayout(tree, altitude);
177 const NodeId numLeaves = layout.numLeaves;
178 const NodeId numVertices = layout.numVertices;
179
180 std::vector<NodeId> parent(static_cast<std::size_t>(numVertices), InvalidNode);
181 std::vector<T> exportedAltitude(static_cast<std::size_t>(numVertices), T{});
182
183 for (NodeId oldNodeId : layout.sortedNodes) {
184 const NodeId newNodeId = layout.nodeToHigra[static_cast<std::size_t>(oldNodeId)];
185 exportedAltitude[static_cast<std::size_t>(newNodeId)] = getAltitude(altitude, oldNodeId);
186 }
187
189 const NodeId properPart = layout.properParts[static_cast<std::size_t>(leafIndex)];
191 if (ownerNodeId == InvalidNode || !tree.isAlive(ownerNodeId)) {
192 throw std::runtime_error("Each proper part must belong to one alive node when exporting a compact Higra hierarchy.");
193 }
194 parent[static_cast<std::size_t>(leafIndex)] = layout.nodeToHigra[static_cast<std::size_t>(ownerNodeId)];
195 exportedAltitude[static_cast<std::size_t>(leafIndex)] = getAltitude(altitude, ownerNodeId);
196 }
197
198 for (NodeId oldNodeId : layout.sortedNodes) {
199 const NodeId newNodeId = layout.nodeToHigra[static_cast<std::size_t>(oldNodeId)];
201 parent[static_cast<std::size_t>(newNodeId)] = oldParentNodeId == oldNodeId ? newNodeId : layout.nodeToHigra[static_cast<std::size_t>(oldParentNodeId)];
202 }
203
204 return {std::move(parent), std::move(exportedAltitude)};
205 }
206
210 template<AltitudeValue T>
211 static void validateMonotoneAltitude(const MorphologicalTree& tree, std::span<const T> altitude) {
212 validateAltitudeBufferShape(tree, altitude);
213 const MorphologicalTreeKind treeType = tree.getTreeType();
214 bool increasingTowardLeaves = false;
215 switch (treeType) {
216 case MorphologicalTreeKind::MAX_TREE:
218 break;
219 case MorphologicalTreeKind::MIN_TREE:
221 break;
222 case MorphologicalTreeKind::TREE_OF_SHAPES:
223 case MorphologicalTreeKind::SELF_DUAL_RESIDUAL_TREE:
224 return;
225 default:
226 throw std::invalid_argument("Unsupported tree type for monotone altitude validation.");
227 }
228
229 for (NodeId nodeId : tree.getAliveNodeIds()) {
230 if (tree.isRoot(nodeId)) {
231 continue;
232 }
233
235 if (parentNodeId == InvalidNode || !tree.isAlive(parentNodeId)) {
236 throw std::runtime_error("Monotonic validation requires every alive non-root node to have an alive parent.");
237 }
238
240 if (getAltitude(altitude, parentNodeId) > getAltitude(altitude, nodeId)) {
241 throw std::runtime_error("Max-tree altitude buffer must be non-decreasing from parent to child.");
242 }
243 } else if (getAltitude(altitude, parentNodeId) < getAltitude(altitude, nodeId)) {
244 throw std::runtime_error("Min-tree altitude buffer must be non-increasing from parent to child.");
245 }
246 }
247 }
248};
249
250} // namespace mmcfilters
constexpr NodeId InvalidNode
Sentinel value used to denote an invalid node identifier.
Definition Common.hpp:25
static Ptr create(int rows, int cols)
Creates an owned image with uninitialised pixel values.
Definition Image.hpp:79
Mutable morphological tree built directly on proper parts and dense node ids.
int getNumTotalProperParts() const
Returns the size of the proper-part domain.
int getNumInternalNodeSlots() 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.
bool isRoot(NodeId nodeId) const
Tests whether nodeId is the current root.
NodeId getProperPartOwner(NodeId properPartId) const
Returns the live node that directly owns properPartId.
MorphologicalTreeKind getTreeType() const noexcept
Returns the current tree type.
int getNumColsOfImage() const
Returns the number of image columns in the attached 2D domain.
void requireNotEditing(const char *context) const
Rejects operations that require a committed connected topology.
AliveNodeRange getAliveNodeIds() const
Returns a fail-fast range over all live node ids.
NodeId getNodeParent(NodeId nodeId) const
Returns the direct parent of nodeId.
int getNumRowsOfImage() const
Returns the number of image rows in the attached 2D domain.
Pure operations over a topology and an explicit altitude buffer.
static T getAltitude(std::span< const T > altitude, NodeId nodeId)
Reads one node altitude from an explicit altitude buffer.
static void validateAltitudeBufferShape(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 validateFiniteImageAltitudes(const ImagePtr< T > &image, const char *context)
Rejects non-finite floating-point pixels before using an image as altitude source.
static void validateMonotoneAltitude(const MorphologicalTree &tree, std::span< const T > altitude)
Validates altitude monotonicity for max-trees and min-trees.
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 void validateFiniteAltitudeValues(std::span< const T > altitude, const char *context)
Rejects non-finite floating-point altitudes in a contiguous input range.
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 AltitudeDiff< T > getNodeResidue(const MorphologicalTree &tree, std::span< const T > altitude, NodeId nodeId)
Computes the altitude difference between one node and its parent.
static ImagePtr< T > reconstructImage(const MorphologicalTree &tree, std::span< const T > altitude, const char *context="TreeAltitudeAlgorithms::reconstructImage")
Reconstructs a typed image from topology ownership and explicit node altitudes.
Owning result for one computed scalar attribute layout and buffer.