3#include "MorphologicalTree.hpp"
4#include "TreeAltitudeAlgorithms.hpp"
5#include "TreeEditor.hpp"
6#include "ValuedMorphologicalTreeView.hpp"
7#include "NativeHierarchyOwned.hpp"
8#include "../utils/Contract.hpp"
9#include "../utils/Image.hpp"
21template <AltitudeValue T>
class ValuedMorphologicalTreeEditor;
23template <AltitudeValue T>
class ValuedMorphologicalTree;
27template <AltitudeValue T> [[nodiscard]] ValuedMorphologicalTreeEditor<T> beginEstablishedValuedEdit(ValuedMorphologicalTree<T>& tree);
66 static bool skipsMonotoneValidation(
const MorphologicalTree& tree)
noexcept {
return tree.nodeAltitudeOrder() == NodeAltitudeOrder::Unconstrained; }
78 [[
nodiscard]]
bool validateLocalMonotoneNodeAltitudeUpdate(
NodeId nodeId, T value)
const {
79 if (skipsMonotoneValidation(tree_)) {
83 const bool increasingFromRoot = tree_.
nodeAltitudeOrder() == NodeAltitudeOrder::Increasing;
84 if (!tree_.
isRoot(nodeId)) {
85 const NodeId parentNodeId = tree_.
parent(nodeId);
86 if (parentNodeId == InvalidNode || !tree_.
isAlive(parentNodeId)) {
87 throw std::runtime_error(
"Monotone altitude update requires an alive non-root node to have an alive parent.");
89 const T parentAltitude = nodeAltitudes_[
static_cast<size_t>(parentNodeId)];
90 if (increasingFromRoot && parentAltitude >= value) {
91 throw std::runtime_error(
"Hierarchy altitude update must remain strictly increasing from parent to child.");
93 if (!increasingFromRoot && parentAltitude <= value) {
94 throw std::runtime_error(
"Hierarchy altitude update must remain strictly decreasing from parent to child.");
98 for (NodeId childId : tree_.children(nodeId)) {
99 const T childAltitude = nodeAltitudes_[
static_cast<size_t>(childId)];
100 if (increasingFromRoot && value >= childAltitude) {
101 throw std::runtime_error(
"Hierarchy altitude update must remain strictly increasing from parent to child.");
103 if (!increasingFromRoot && value <= childAltitude) {
104 throw std::runtime_error(
"Hierarchy altitude update must remain strictly decreasing from parent to child.");
114 ValuedMorphologicalTree() =
delete;
146 if (
this != &
other) {
147 tree_ = std::move(
other.tree_);
148 nodeAltitudes_ = std::move(
other.nodeAltitudes_);
215 throw std::runtime_error(
"Altitude buffer size must match the dense internal-node domain.");
235 throw std::invalid_argument(
"ValuedMorphologicalTree::nodeAltitude requires a live internal NodeId."));
236 return nodeAltitudes_[
static_cast<size_t>(nodeId)];
247 if (!tree_.
isAlive(nodeId) ||
static_cast<size_t>(nodeId) >= nodeAltitudes_.size()) {
248 throw std::invalid_argument(
"ValuedMorphologicalTree::setNodeAltitude requires a live internal NodeId.");
251 static_cast<void>(validateLocalMonotoneNodeAltitudeUpdate(nodeId, value));
252 nodeAltitudes_[
static_cast<size_t>(nodeId)] = value;
307 template <
class Contribution>
308 requires(std::is_arithmetic_v<Contribution> && !std::is_same_v<std::remove_cv_t<Contribution>,
bool>)
311 "ValuedMorphologicalTree::reconstructFromNodeContributions");
350 struct AltitudeRollbackJournal {
352 std::size_t originalSize = 0;
354 TreeEditor::DeltaNodeSet captured;
356 std::vector<std::pair<NodeId, T>> values;
364 std::size_t originalAltitudeSize_ = 0;
366 std::unique_ptr<AltitudeRollbackJournal> altitudeRollbackJournal_;
368 std::size_t editRevision_ = 0;
370 std::optional<std::size_t> provenRevision_;
386 void ensureAltitudeRollbackJournal() {
390 editor_.ensureRollbackJournal();
391 if (!altitudeRollbackJournal_) {
392 altitudeRollbackJournal_ = std::make_unique<AltitudeRollbackJournal>();
393 altitudeRollbackJournal_->originalSize = originalAltitudeSize_;
402 void captureAltitudeForRollback(NodeId nodeId) {
403 ensureAltitudeRollbackJournal();
404 if (!altitudeRollbackJournal_) {
407 if (nodeId < 0 ||
static_cast<std::size_t
>(nodeId) >= originalAltitudeSize_) {
410 if (altitudeRollbackJournal_->captured.contains(nodeId)) {
413 altitudeRollbackJournal_->values.reserve(altitudeRollbackJournal_->values.size() + 1);
414 if (!altitudeRollbackJournal_->captured.insert(nodeId)) {
417 altitudeRollbackJournal_->values.emplace_back(nodeId, valuedTree_.nodeAltitudes_[
static_cast<std::size_t
>(nodeId)]);
423 void restoreAltitudeJournal() noexcept {
424 if (!altitudeRollbackJournal_ || !editor_.
canRollback()) {
427 valuedTree_.nodeAltitudes_.resize(altitudeRollbackJournal_->originalSize);
428 for (
const auto& [nodeId, altitude] : altitudeRollbackJournal_->values) {
429 valuedTree_.nodeAltitudes_[
static_cast<std::size_t
>(nodeId)] = altitude;
436 void recordMutation() noexcept {
437 if (editor_.invariantsEstablishedByConstruction_) {
441 provenRevision_.reset();
460 : valuedTree_(
other.valuedTree_), editor_(std::move(
other.editor_)), originalAltitudeSize_(
other.originalAltitudeSize_),
461 altitudeRollbackJournal_(std::move(
other.altitudeRollbackJournal_)), editRevision_(
other.editRevision_), provenRevision_(
other.provenRevision_) {
462 other.altitudeRollbackJournal_.reset();
463 other.provenRevision_.reset();
488 throw std::logic_error(
"ValuedMorphologicalTreeEditor::rollback is unavailable for the internal journal-free editor.");
490 restoreAltitudeJournal();
506 static_cast<std::size_t
>(valuedTree_.tree_.numInternalNodeSlots()) + (valuedTree_.tree_.getNumFreeNodeSlots() == 0 ? 1u : 0
u);
508 ensureAltitudeRollbackJournal();
510 valuedTree_.nodeAltitudes_.resize(
static_cast<size_t>(valuedTree_.tree_.numInternalNodeSlots()), T{});
511 captureAltitudeForRollback(nodeId);
512 valuedTree_.nodeAltitudes_[
static_cast<size_t>(nodeId)] = altitude;
528 if (!valuedTree_.tree_.isAlive(nodeId)) {
529 throw std::invalid_argument(
"ValuedMorphologicalTreeEditor::setNodeAltitude requires a live node.");
532 captureAltitudeForRollback(nodeId);
533 valuedTree_.nodeAltitudes_[
static_cast<size_t>(nodeId)] = altitude;
534 editor_.touch(nodeId);
675 if (!editor_.invariantsEstablishedByConstruction_) {
676 for (
NodeId node : editor_.touchedNodes_.entries()) {
677 if (!valuedTree_.tree_.isAlive(node)) {
681 valuedTree_.validateLocalMonotoneNodeAltitudeUpdate(node, valuedTree_.nodeAltitudes_[
static_cast<std::size_t
>(node)]) &&
strictAltitudeExcludesCycles;
689 valuedTree_.validateMonotoneNodeAltitudes();
691 if (
proof.usedCompleteValidation()) {
692 valuedTree_.validateMonotoneNodeAltitudes();
695 provenRevision_ = editRevision_;
705 if (!provenRevision_ || *provenRevision_ != editRevision_) {
706 throw std::logic_error(
"Incremental valued-tree proof is stale or belongs to another edit revision.");
709 provenRevision_.reset();
710 altitudeRollbackJournal_.reset();
733 valuedTree_.validateMonotoneNodeAltitudes();
734 }
catch (
const std::exception&
ex) {
735 return {
false,
ex.what()};
737 return {
false,
"ValuedMorphologicalTreeEditor monotone-altitude validation failed with an unknown error."};
739 editor_.finishCommit(TreeEditValidationMode::Complete);
740 altitudeRollbackJournal_.reset();
741 provenRevision_.reset();
751 throw std::runtime_error(
result.message);
771template <AltitudeValue T>
772[[nodiscard]] ValuedMorphologicalTree<T> materializeNativeHierarchy(NativeHierarchyOwned<T>&& hierarchy,
773 std::optional<NodeId> preservedExternalNodeIdOffset,
774 MaterializedVersionPolicy versionPolicy) {
775 const MorphologicalTreeConstructionTag tag{};
776 const std::size_t numNodes = hierarchy.parent.size();
777 MorphologicalTree topology(tag, std::move(hierarchy.parent), std::move(hierarchy.smallestNodeMap), hierarchy.root, hierarchy.gridDomain2D,
778 std::move(hierarchy.semantics));
779 if (versionPolicy == MaterializedVersionPolicy::PreserveLinkedConstruction) {
780 topology.mutationVersion_ = numNodes + 1;
782 if (preservedExternalNodeIdOffset) {
783 topology.preserveExternalNodeIdOffset(*preservedExternalNodeIdOffset);
785 return ValuedMorphologicalTree<T>(tag, std::move(topology), std::move(hierarchy.nodeAltitudes));
798template <AltitudeValue T> [[nodiscard]] ValuedMorphologicalTreeEditor<T> beginEstablishedValuedEdit(ValuedMorphologicalTree<T>& tree) {
799 return ValuedMorphologicalTreeEditor<T>(tree,
false,
true);
#define MMCFILTERS_CONTRACT_REQUIRE(condition,...)
Evaluates a caller precondition and its failure action only in checked builds.
Mutable connected-subset tree on a finite 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.
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.
void requireNotEditing(const char *context) const
Rejects operations that require a committed connected topology.
NodeId parent(NodeId nodeId) const
Returns the direct parent of nodeId.
void mergeNodeIntoParent(NodeId nodeId)
Merges nodeId into its parent and releases the emptied slot.
static void validateNodeAltitudeBufferShape(const MorphologicalTree &tree, std::span< const T > altitude)
Validates that an altitude buffer covers the dense internal-node domain.
static void validateMonotoneNodeAltitudes(const MorphologicalTree &tree, std::span< const T > altitude)
Validates the hierarchy's declared global altitude order.
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 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 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.
Move-only evidence that the current edit revision satisfies the generic topology invariants.
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.
void commit(IncrementalProof &&proof)
Commits the exact edit revision represented by proof.
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 movePixelToProperPart(NodeId targetNodeId, NodeId sourceNodeId, PixelId pixel)
Transfers one direct proper part from sourceNodeId to targetNodeId.
void releaseNode(NodeId nodeId)
Releases an empty detached non-root node slot.
void moveChildren(NodeId parentId, NodeId sourceId)
Transfers every direct child of sourceId under parentId.
void rollback()
Aborts a recoverable edit and restores its original state.
bool canRollback() const noexcept
Tests whether this editor owns a delta rollback journal.
void pruneNode(NodeId nodeId)
Applies the committed-safe subtree prune inside the staged edit.
IncrementalProof proveIncremental()
Produces move-only evidence for the current edit revision.
void mergeProperParts(NodeId targetNodeId, NodeId sourceNodeId)
Transfers every direct proper part from sourceNodeId to targetNodeId.
Edit-session facade for ValuedMorphologicalTree.
~ValuedMorphologicalTreeEditor()
Restores the altitude journal and closes the valued-tree edit session.
void attach(NodeId parentId, NodeId detachedNodeId)
Attaches one detached node through the structural editor.
ValuedMorphologicalTreeEditor & operator=(const ValuedMorphologicalTreeEditor &)=delete
Disables copy assignment.
TreeValidationResult validate() const noexcept
Validates only the staged topology.
NodeId createDetachedNode(T altitude=T{})
Creates a detached topology node and initializes its altitude.
void setNodeAltitude(NodeId nodeId, T altitude)
Sets a live node altitude during a staged topology edit.
ValuedMorphologicalTreeEditor(ValuedMorphologicalTreeEditor &&other) noexcept
Transfers the active valued-tree edit session and rollback journal.
void commit()
Exception-based wrapper around validateAndCommit().
void moveChildren(NodeId parentId, NodeId sourceId)
Moves all direct children from sourceId under parentId.
void rollback()
Restores topology and altitude captured by the delta journals.
void removeChild(NodeId parentNodeId, NodeId childId, bool releaseNodeFlag)
Detaches a direct child and optionally releases an empty node slot.
bool canRollback() const noexcept
Tests whether topology and altitude can be rolled back.
void setRoot(NodeId nodeId)
Promotes one node to the topology root.
void mergeNodeIntoParent(NodeId nodeId)
Applies the topology merge helper inside the valued-tree edit session.
bool hasDetachedAliveNodes() const noexcept
Returns whether the structural edit still has detached live nodes.
void reparent(NodeId nodeId, NodeId newParentId)
Reparents one node through the structural editor.
void detach(NodeId nodeId)
Detaches one non-root node through the structural editor.
TreeEditor::IncrementalProof proveIncremental()
Produces a generic move-only proof for the current valued-tree edit revision.
TreeValidationResult validateAndCommit() noexcept
Validates topology, validates altitude order, then closes the edit.
ValuedMorphologicalTreeEditor & operator=(ValuedMorphologicalTreeEditor &&)=delete
Disables move assignment.
ValuedMorphologicalTreeEditor(const ValuedMorphologicalTreeEditor &)=delete
Disables copy construction.
void commit(TreeEditor::IncrementalProof &&proof)
Commits the exact valued-tree edit revision represented by proof.
void pruneNode(NodeId nodeId)
Applies the topology prune helper inside the valued-tree edit session.
void releaseNode(NodeId nodeId)
Releases an empty detached node slot.
void mergeProperParts(NodeId targetNodeId, NodeId sourceNodeId)
Moves all direct proper parts between nodes.
void movePixelToProperPart(NodeId targetNodeId, NodeId sourceNodeId, PixelId pixel)
Moves one direct proper part between nodes.
Wrapper pairing MorphologicalTree topology with an external altitude buffer.
ValuedMorphologicalTree & operator=(ValuedMorphologicalTree &&other)
Move-assigns committed topology and altitude ownership.
void setNodeAltitudes(NodeAltitudeBuffer altitudeBuffer)
Replaces the owned altitude buffer after full validation.
ValuedMorphologicalTree(const ValuedMorphologicalTree &)=delete
Disables copy construction.
void validateMonotoneNodeAltitudes() const
Validates the current altitude buffer against the topology order.
ValuedMorphologicalTreeView< T > asView() const
Creates a non-owning view using the established altitude guarantees of this owner.
T nodeAltitude(NodeId nodeId) const
Returns one live node altitude from the dense buffer.
T AltitudeType
Altitude scalar type stored by this valued morphological tree.
void validateNodeAltitudeBufferShape() const
Checks that the altitude buffer covers the dense internal-node domain.
void pruneNode(NodeId nodeId)
Prunes a complete subtree through the owned topology.
NodeAltitudeSpan< T > nodeAltitudeSpan() const noexcept
Returns a read-only span over the dense altitude buffer.
ValuedMorphologicalTreeEditor< T > edit()
Opens the only public entrypoint for staged valued-tree edits.
AltitudeDifference< T > nodeResidue(NodeId nodeId) const
Returns the altitude difference between a node and its parent.
const MorphologicalTree & topology() const noexcept
Returns read-only access to the owned topology.
ImagePtr< T > reconstructFromNodeAltitudes() const
Reconstructs an image by assigning each proper part its smallest-node altitude.
std::pair< std::vector< NodeId >, std::vector< T > > exportHigraHierarchy() const
Exports the current live rooted tree to a new compact Higra parent/altitude representation.
void setNodeAltitude(NodeId nodeId, T value)
Updates one live node altitude with local monotonicity validation.
ImagePtr< Contribution > reconstructFromNodeContributions(std::span< const Contribution > nodeContributions) const
Reconstructs from arbitrary dense node contributions using the fixed zero baseline.
ValuedMorphologicalTree(ValuedMorphologicalTree &&other)
Transfers committed topology and altitude ownership.
ValuedMorphologicalTree & operator=(const ValuedMorphologicalTree &)=delete
Disables copy assignment.
ValuedMorphologicalTree(detail::MorphologicalTreeConstructionTag, MorphologicalTree &&topology, NodeAltitudeBuffer &&altitude)
Consumes established topology and altitude buffers without revalidation.
const NodeAltitudeBuffer & nodeAltitudes() const noexcept
Returns the dense altitude buffer indexed by internal NodeId.
void mergeNodeIntoParent(NodeId nodeId)
Merges one node into its parent through the owned topology.
Owning result for one computed scalar attribute layout and buffer.
Non-throwing validation result returned by edit-session checks.