3#include "MorphologicalTree.hpp"
4#include "TreeAltitudeAlgorithms.hpp"
5#include "TreeEditor.hpp"
6#include "WeightedTreeView.hpp"
15template<AltitudeValue T>
16class WeightedTreeEditor;
18template<AltitudeValue T>
19class WeightedMorphologicalTree;
32enum class AltitudeDomain {
51template<AltitudeValue T>
53 std::span<const T> values;
54 AltitudeDomain domain;
72template<AltitudeValue T>
99 throw std::runtime_error(
"Cannot infer node altitude from a topology node without direct proper parts.");
101 altitude_[
static_cast<size_t>(
nodeId)] =
static_cast<T>((*
img)[*
it]);
110 throw std::invalid_argument(
"Internal altitude buffer size must match the dense internal-node domain.");
113 altitude_.assign(altitudeValues.begin(), altitudeValues.end());
119 void importAltitudeFromHigra(std::span<const T> higraAltitude) {
120 if (
static_cast<size_t>(tree_.
getNumHigraNodes()) != higraAltitude.size()) {
121 throw std::invalid_argument(
"Higra altitude buffer size must match the preserved imported Higra hierarchy.");
127 altitude_[
static_cast<size_t>(slotId)] =
static_cast<T
>(higraAltitude[
static_cast<size_t>(higraNodeId)]);
134 static bool skipsMonotoneValidation(
const MorphologicalTree& tree)
noexcept {
135 const MorphologicalTreeKind treeType = tree.getTreeType();
136 return treeType == MorphologicalTreeKind::TREE_OF_SHAPES || treeType == MorphologicalTreeKind::SELF_DUAL_RESIDUAL_TREE;
145 void validateLocalMonotoneAltitudeUpdate(NodeId nodeId, T value)
const {
146 if (skipsMonotoneValidation(tree_)) {
150 const bool increasingTowardLeaves = tree_.
getTreeType() == MorphologicalTreeKind::MAX_TREE;
151 if (!tree_.
isRoot(nodeId)) {
153 if (parentNodeId == InvalidNode || !tree_.
isAlive(parentNodeId)) {
154 throw std::runtime_error(
"Monotone altitude update requires an alive non-root node to have an alive parent.");
156 const T parentAltitude = altitude_[
static_cast<size_t>(parentNodeId)];
157 if (increasingTowardLeaves && parentAltitude > value) {
158 throw std::runtime_error(
"Max-tree altitude update must keep altitude non-decreasing from parent to child.");
160 if (!increasingTowardLeaves && parentAltitude < value) {
161 throw std::runtime_error(
"Min-tree altitude update must keep altitude non-increasing from parent to child.");
165 for (NodeId childId : tree_.getChildren(nodeId)) {
166 const T childAltitude = altitude_[
static_cast<size_t>(childId)];
167 if (increasingTowardLeaves && value > childAltitude) {
168 throw std::runtime_error(
"Max-tree altitude update must keep altitude non-decreasing from parent to child.");
170 if (!increasingTowardLeaves && value < childAltitude) {
171 throw std::runtime_error(
"Min-tree altitude update must keep altitude non-increasing from parent to child.");
177 WeightedMorphologicalTree() =
delete;
180 WeightedMorphologicalTree(
const WeightedMorphologicalTree&) =
delete;
181 WeightedMorphologicalTree& operator=(
const WeightedMorphologicalTree&) =
delete;
202 throw std::invalid_argument(
"WeightedMorphologicalTree construction requires a non-null image.");
204 if (
img->getNumRows() <= 0 ||
img->getNumCols() <= 0 ||
img->getSize() <= 0) {
205 throw std::invalid_argument(
"WeightedMorphologicalTree construction requires a non-empty 2D image.");
209 throw std::invalid_argument(
"WeightedMorphologicalTree construction requires an image domain matching the topology.");
212 assignAltitudeFromDirectProperParts(
img);
227 switch (altitude.domain) {
228 case detail::AltitudeDomain::InternalNodeSlots:
229 assignInternalAltitude(altitude.values);
231 case detail::AltitudeDomain::HigraNodeIds:
232 importAltitudeFromHigra(altitude.values);
267 return std::span<const T>(altitude_);
286 throw std::runtime_error(
"Altitude buffer size must match the dense internal-node domain.");
301 tree_.
requireNotEditing(
"WeightedMorphologicalTree::setAltitudeBufferUnchecked");
303 throw std::runtime_error(
"Altitude buffer size must match the dense internal-node domain.");
321 throw std::invalid_argument(
"WeightedMorphologicalTree::getAltitude requires a live internal NodeId.");
323 return altitude_[
static_cast<size_t>(
nodeId)];
332 throw std::invalid_argument(
"WeightedMorphologicalTree::setAltitude requires a live internal NodeId.");
335 validateLocalMonotoneAltitudeUpdate(
nodeId,
value);
349 throw std::invalid_argument(
"WeightedMorphologicalTree::setAltitudeUnchecked requires a live internal NodeId.");
418template<AltitudeValue T>
441 const std::size_t
requiredAltitudeSize =
static_cast<std::size_t
>(weighted_.tree_.getNumInternalNodeSlots()) + (weighted_.tree_.getNumFreeNodeSlots() == 0 ? 1u : 0
u);
444 weighted_.altitude_.resize(
static_cast<size_t>(weighted_.tree_.getNumInternalNodeSlots()),
T{});
445 weighted_.altitude_[
static_cast<size_t>(nodeId)] = altitude;
457 if (!weighted_.tree_.isAlive(
nodeId)) {
458 throw std::invalid_argument(
"WeightedTreeEditor::setNodeAltitude requires a live node.");
461 weighted_.altitude_[
static_cast<size_t>(
nodeId)] = altitude;
566 weighted_.validateMonotoneAltitude();
567 }
catch (
const std::exception&
ex) {
568 return {
false,
ex.what()};
570 return {
false,
"WeightedTreeEditor monotone-altitude validation failed with an unknown error."};
582 throw std::runtime_error(
result.message);
int NodeId
Node identifier type used throughout the project.
std::shared_ptr< Image< PixelType > > ImagePtr
Shared pointer alias for an image with arbitrary pixel type.
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.
NodeId getHigraNodeId(NodeId nodeId) const noexcept
Returns the preserved imported Higra node id for one live tree node.
void pruneNode(NodeId nodeId)
Prunes the subtree of nodeId, moving all its support to the parent.
bool isRoot(NodeId nodeId) const
Tests whether nodeId is the current root.
int getNumHigraNodes() const
Returns the size of the preserved imported Higra node-id domain.
ProperPartsRange getProperParts(NodeId nodeId) const
Returns a fail-fast range over the direct proper parts of nodeId.
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.
void mergeNodeIntoParent(NodeId nodeId)
Merges nodeId into its parent and releases the emptied slot.
static void validateAltitudeBufferShape(const MorphologicalTree &tree, std::span< const T > altitude)
Validates that an altitude buffer covers the dense internal-node domain.
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.
Thin edit-session facade for multi-step topology updates.
TreeValidationResult validate() const noexcept
Runs strong validation without closing the session.
void detach(NodeId nodeId)
Detaches one non-root node from the connected rooted component.
bool hasDetachedAliveNodes() const noexcept
Returns whether the staged edit still has detached alive nodes.
void mergeNodeIntoParent(NodeId nodeId)
Applies the committed-safe parent merge inside the staged edit.
void reparent(NodeId nodeId, NodeId newParentId)
Reparents one live non-root node under another live node.
void attach(NodeId parentId, NodeId detachedNodeId)
Attaches one detached node back under the connected rooted tree.
NodeId createDetachedNode()
Creates a live detached node in the topological hierarchy.
void setRoot(NodeId nodeId)
Promotes nodeId to become the connected root.
void removeChild(NodeId parentNodeId, NodeId childId, bool releaseNodeFlag)
Detaches a direct child from its parent and optionally releases an empty detached slot.
void moveProperParts(NodeId targetNodeId, NodeId sourceNodeId)
Transfers every direct proper part from sourceNodeId to targetNodeId.
void releaseNode(NodeId nodeId)
Releases an empty detached non-root node slot.
void moveProperPart(NodeId targetNodeId, NodeId sourceNodeId, NodeId properPartId)
Transfers one direct proper part from sourceNodeId to targetNodeId.
void moveChildren(NodeId parentId, NodeId sourceId)
Transfers every direct child of sourceId under parentId.
void commitUnchecked() noexcept
Finalizes a staged edit without running the linear validation.
void pruneNode(NodeId nodeId)
Applies the committed-safe subtree prune inside the staged edit.
Wrapper pairing MorphologicalTree topology with an external altitude buffer.
void setAltitudeBuffer(altitude_buffer altitudeBuffer)
Replaces the owned altitude buffer after full validation.
AltitudeSpan< T > altitudeSpan() const noexcept
Returns a read-only span over the dense altitude buffer.
ImagePtr< T > reconstructionImage() const
Reconstructs an image by assigning each proper part its owner altitude.
void mergeNodeIntoParent(NodeId nodeId)
Merges one node into its parent through the owned topology.
void setAltitudeBufferUnchecked(altitude_buffer altitudeBuffer)
Replaces the owned altitude buffer without checking tree-order monotonicity.
std::vector< T > altitude_buffer
Dense altitude buffer indexed by internal NodeId.
std::pair< std::vector< NodeId >, std::vector< T > > exportHigraHierarchy() const
Exports the current live rooted tree to a new compact Higra parent/altitude representation.
WeightedMorphologicalTree(detail::MorphologicalTreeConstructionTag, MorphologicalTree &&topology, detail::AltitudeInput< T > altitude)
Creates a weighted tree from an explicit altitude input view.
void pruneNode(NodeId nodeId)
Prunes a complete subtree through the owned topology.
void validateMonotoneAltitude() const
Validates the current altitude buffer against the topology order.
void validateAltitudeBufferShape() const
Checks that the altitude buffer covers the dense internal-node domain.
const altitude_buffer & getAltitudeBuffer() const noexcept
Returns the dense altitude buffer indexed by internal NodeId.
WeightedTreeView< T > asView() const
Creates a non-owning weighted view over this owner.
WeightedMorphologicalTree(WeightedMorphologicalTree &&) noexcept=default
Transfers topology and altitude ownership from another weighted tree.
void setAltitude(NodeId nodeId, T value)
Updates one live node altitude with local monotonicity validation.
const MorphologicalTree & topology() const noexcept
Returns read-only access to the owned topology.
WeightedTreeEditor< T > edit()
Opens the only public entrypoint for staged weighted edits.
void setAltitudeUnchecked(NodeId nodeId, T value)
Updates one node altitude without checking tree-order monotonicity.
T getAltitude(NodeId nodeId) const
Returns one live node altitude from the dense buffer.
AltitudeDiff< T > getNodeResidue(NodeId nodeId) const
Returns the altitude difference between a node and its parent.
Edit-session facade for WeightedMorphologicalTree.
TreeValidationResult validateAndCommit() noexcept
Validates topology, validates altitude order, then closes the edit.
void moveProperPart(NodeId targetNodeId, NodeId sourceNodeId, NodeId properPartId)
Moves one direct proper part between nodes.
void commit()
Exception-based wrapper around validateAndCommit().
NodeId createDetachedNode(T altitude=T{})
Creates a detached topology node and initializes its altitude.
void setRoot(NodeId nodeId)
Promotes one node to the topology root.
void reparent(NodeId nodeId, NodeId newParentId)
Reparents one node through the structural editor.
void removeChild(NodeId parentNodeId, NodeId childId, bool releaseNodeFlag)
Detaches a direct child and optionally releases an empty node slot.
void commitUnchecked() noexcept
Closes the weighted edit without topology or altitude validation.
void moveChildren(NodeId parentId, NodeId sourceId)
Moves all direct children from sourceId under parentId.
void setNodeAltitude(NodeId nodeId, T altitude)
Sets a live node altitude during a staged topology edit.
void pruneNode(NodeId nodeId)
Applies the topology prune helper inside the weighted edit session.
TreeValidationResult validate() const noexcept
Validates only the staged topology.
void mergeNodeIntoParent(NodeId nodeId)
Applies the topology merge helper inside the weighted edit session.
void moveProperParts(NodeId targetNodeId, NodeId sourceNodeId)
Moves all direct proper parts between nodes.
void detach(NodeId nodeId)
Detaches one non-root node through the structural editor.
bool hasDetachedAliveNodes() const noexcept
Returns whether the structural edit still has detached live nodes.
void releaseNode(NodeId nodeId)
Releases an empty detached node slot.
void attach(NodeId parentId, NodeId detachedNodeId)
Attaches one detached node through the structural editor.
Owning result for one computed scalar attribute layout and buffer.
Non-throwing validation result returned by edit-session checks.