55#include "../utils/Common.hpp"
56#include "../trees/MorphologicalTree.hpp"
57#include "../trees/WeightedTreeView.hpp"
58#include "../trees/detail/ProperPartEntryNode.hpp"
59#include "../trees/detail/TreeTraversalDetail.hpp"
60#include "../utils/AdjacencyRelation.hpp"
61#include "detail/PendingPixelLists.hpp"
62#include "detail/ContourDeltaStore.hpp"
78 using PendingPixelLists = detail::PendingPixelLists;
99 std::size_t treeMutationVersion_ = 0;
101 ContourDeltaStore localDeltas_;
103 mutable std::vector<int> cachedContourValues_;
105 mutable std::vector<uint32_t> cachedContourOffset_;
107 mutable std::vector<uint32_t> cachedContourSize_;
109 mutable std::vector<uint8_t> cachedContourReady_;
111 mutable std::vector<uint16_t> pixelMark_;
113 mutable uint16_t markGeneration_ = 1;
128 if (capacityHint > 0) {
129 cachedContourValues_.reserve(
static_cast<size_t>(capacityHint));
174 return owner_->cachedContourBegin(node_);
182 return owner_->cachedContourEnd(node_);
191 void ensureReadable_()
const {
192 owner_->ensureSubtreeMaterialized(node_);
264 return lhs.node_ ==
rhs.node_;
282 !owner_->tree.isAlive(node_)) {
290 const IncrementalContours* owner_ =
nullptr;
304 owner_->requireStableTree(
"IncrementalContours::contoursByNode");
312 owner_->requireStableTree(
"IncrementalContours::contoursByNode");
313 return iterator(owner_, owner_->tree.getNumInternalNodeSlots());
328 requireStableTree(
"IncrementalContours::storageStats");
330 stats.addDeltaValues = localDeltas_.addValues.size();
331 stats.removeDeltaValues = localDeltas_.removeValues.size();
332 stats.cachedContourValues = cachedContourValues_.size();
333 stats.cachedContourCapacity = cachedContourValues_.capacity();
334 stats.cachedContourReadyNodes =
static_cast<std::size_t
>(
335 std::count(cachedContourReady_.begin(), cachedContourReady_.end(),
uint8_t{1}));
336 stats.approxAllocatedBytes =
337 localDeltas_.addValues.capacity() *
sizeof(
int) +
338 localDeltas_.removeValues.capacity() *
sizeof(
int) +
339 localDeltas_.addSpans.capacity() *
sizeof(ContourDeltaStore::Span) +
340 localDeltas_.removeSpans.capacity() *
sizeof(ContourDeltaStore::Span) +
341 cachedContourValues_.capacity() *
sizeof(
int) +
342 cachedContourOffset_.capacity() *
sizeof(
uint32_t) +
343 cachedContourSize_.capacity() *
sizeof(
uint32_t) +
344 cachedContourReady_.capacity() *
sizeof(
uint8_t) +
345 pixelMark_.capacity() *
sizeof(
uint16_t);
351 requireStableTree(
"IncrementalContours::getContour");
352 requireLiveContourNode(node,
"IncrementalContours::getContour");
358 requireStableTree(
"IncrementalContours::contoursByNode");
369 requireStableTree(
"IncrementalContours::materializeAll");
370 ensureSubtreeMaterialized(tree.
getRoot());
377 requireStableTree(
"IncrementalContours::isMaterialized");
379 if (!cachedContourReady_[node]) {
390 requireStableTree(
"IncrementalContours::isContourMaterialized");
391 requireLiveContourNode(node,
"IncrementalContours::isContourMaterialized");
392 return static_cast<bool>(cachedContourReady_[node]);
396 void requireStableTree(
const char*
context)
const {
403 void requireLiveContourNode(
NodeId node,
const char*
context)
const {
405 throw std::invalid_argument(std::string(
context) +
" requires a live internal NodeId.");
409 std::vector<int>::const_iterator cachedContourBegin(NodeId node)
const {
410 return cachedContourValues_.begin() +
static_cast<std::ptrdiff_t
>(cachedContourOffset_[node]);
413 std::vector<int>::const_iterator cachedContourEnd(NodeId node)
const {
414 return cachedContourBegin(node) +
static_cast<std::ptrdiff_t
>(cachedContourSize_[node]);
417 void nextMarkGeneration()
const {
419 if (markGeneration_ == 0) {
420 std::fill(pixelMark_.begin(), pixelMark_.end(), 0);
425 void addIfUnmarked(std::vector<int>& values,
int pixel)
const {
426 if (pixel < 0 || pixel >=
static_cast<int>(pixelMark_.size())) {
429 if (pixelMark_[pixel] != markGeneration_) {
430 pixelMark_[pixel] = markGeneration_;
431 values.push_back(pixel);
435 void removeIfMarked(
int pixel)
const {
436 if (pixel >= 0 && pixel <
static_cast<int>(pixelMark_.size())) {
437 pixelMark_[pixel] = 0;
441 void commitMaterializedContour(NodeId node,
const std::vector<int>& values)
const {
442 cachedContourOffset_[node] = checkedU32(cachedContourValues_.size(),
"cached contour offset");
443 cachedContourSize_[node] = checkedU32(values.size(),
"cached contour size");
444 cachedContourValues_.insert(cachedContourValues_.end(), values.begin(), values.end());
445 cachedContourReady_[node] = 1;
448 static uint32_t checkedU32(std::size_t value,
const char* context) {
449 if (value >
static_cast<std::size_t
>(std::numeric_limits<uint32_t>::max())) {
450 throw std::overflow_error(std::string(context) +
" exceeds uint32_t.");
452 return static_cast<uint32_t
>(value);
465 void ensureSubtreeMaterialized(NodeId root)
const {
466 requireStableTree(
"IncrementalContours::ensureSubtreeMaterialized");
467 requireLiveContourNode(root,
"IncrementalContours::ensureSubtreeMaterialized");
468 if (cachedContourReady_[root]) {
472 std::vector<std::pair<NodeId, bool>> stack;
473 stack.emplace_back(root,
false);
474 std::vector<int> values;
476 while (!stack.empty()) {
477 const auto [node, expanded] = stack.back();
479 if (cachedContourReady_[node]) {
483 stack.emplace_back(node,
true);
484 for (NodeId child : tree.getChildren(node)) {
485 if (!cachedContourReady_[child]) {
486 stack.emplace_back(child,
false);
493 const auto additions = localDeltas_.additions(node);
494 std::size_t reserveSize = additions.size();
495 for (NodeId child : tree.getChildren(node)) {
496 reserveSize +=
static_cast<std::size_t
>(cachedContourSize_[child]);
498 values.reserve(reserveSize);
499 nextMarkGeneration();
501 for (NodeId child : tree.getChildren(node)) {
502 for (
auto it = cachedContourBegin(child); it != cachedContourEnd(child); ++it) {
503 addIfUnmarked(values, *it);
507 for (
int value : additions) {
508 addIfUnmarked(values, value);
511 for (
int rem : localDeltas_.removals(node)) {
515 std::size_t writeIndex = 0;
516 for (
int value : values) {
517 if (pixelMark_[
static_cast<std::size_t
>(value)] == markGeneration_) {
518 values[writeIndex++] = value;
521 values.resize(writeIndex);
522 commitMaterializedContour(node, values);
528 struct ExtractedContourDeltas {
529 ContourDeltaStore deltas;
530 int capacityHint = 0;
533 [[nodiscard]]
static ExtractedContourDeltas extractContourDeltasImpl(
const MorphologicalTree& tree) {
534 if (tree.getNumRowsOfImage() <= 0 || tree.getNumColsOfImage() <= 0) {
535 throw std::invalid_argument(
"Contour extraction requires a non-empty image domain.");
537 if (!tree.isAlive(tree.getRoot())) {
538 throw std::invalid_argument(
"Contour extraction requires a live tree root.");
541 const int numNodes = tree.getNumInternalNodeSlots();
542 const int totalPixels = tree.getNumRowsOfImage() * tree.getNumColsOfImage();
544 const int capacityHint = std::max(totalPixels / 4, 1);
546 PendingPixelLists localContourPixels(numNodes, capacityHint);
547 PendingPixelLists localRemovalPixels(numNodes, capacityHint);
549 PendingPixelLists pendingContourRemovals(numNodes, capacityHint);
552 std::vector<int> ncount(totalPixels, 0);
554 std::vector<int> removalBuffer;
555 removalBuffer.reserve(64);
557 detail::traversePostOrder(
560 [](NodeId) ->
void {},
561 [](NodeId, NodeId) ->
void {},
562 [&](NodeId nodePId) {
563 const NodeId nodeP = nodePId;
565 removalBuffer.clear();
566 pendingContourRemovals.consumeInto(nodeP, removalBuffer);
567 for (int p : removalBuffer) {
568 bool isPixelToBeRemoved = true;
569 for (int r : adj4.getNeighborPixels(p)) {
570 const NodeId entry = contourEntryNode(tree, p, r);
571 if (entry != InvalidNode && tree.isStrictAncestor(entry, nodePId)) {
572 pendingContourRemovals.add(entry, p);
573 isPixelToBeRemoved = false;
576 if (!adj4.isBorderDomainImage(p) && isPixelToBeRemoved) {
577 localRemovalPixels.add(nodeP, p);
582 for (
int p : tree.getProperParts(nodePId)) {
583 if (adj4.isBorderDomainImage(p)) {
587 for (
int q : adj4.getNeighborPixels(p)) {
588 const NodeId nodeQId = tree.getProperPartOwner(q);
589 const NodeId entry = contourEntryNode(tree, p, q);
590 if (entry == InvalidNode) {
593 if (entry != nodePId) {
595 if (entry != nodeQId) {
596 pendingContourRemovals.add(entry, p);
598 }
else if (tree.isStrictAncestor(nodePId, nodeQId)) {
600 if (ncount[q] == 0) {
601 localRemovalPixels.add(nodeP, q);
607 localContourPixels.add(nodeP, p);
613 ContourDeltaStore::fromPendingPixelLists(localContourPixels, localRemovalPixels, totalPixels),
627 ExtractedContourDeltas
extracted = extractContourDeltasImpl(tree);
631 template<AltitudeValue T>
636 tree.requireTopologyUnchanged(
"ContoursComputedIncrementally::extractContourDeltas");
637 return extractContourDeltas(tree.topology());
666 ExtractedContourDeltas
extracted = extractContourDeltasImpl(tree);
670 template<AltitudeValue T>
675 tree.requireTopologyUnchanged(
"ContoursComputedIncrementally::extractCompactContours");
676 return extractCompactContours(tree.topology());
int NodeId
Node identifier type used throughout the project.
constexpr NodeId InvalidNode
Sentinel value used to denote an invalid node identifier.
Cache-aware range over the contour of one node.
iterator begin() const
Returns the first contour pixel, materializing on demand.
bool empty() const
Returns true when the node contour is empty.
iterator end() const
Returns the end sentinel for the contour range.
ContourRange(const IncrementalContours *owner, NodeId node)
Creates a range for node backed by owner.
std::vector< int >::const_iterator iterator
Immutable iterator over materialized contour pixel indices.
Forward iterator that yields (nodeId, ContourRange) pairs.
friend bool operator==(const iterator &lhs, const iterator &rhs)
Returns true when both iterators refer to the same node position.
iterator(const IncrementalContours *owner, NodeId node)
Creates an iterator over owner starting at node.
iterator & operator++()
Advances to the next live node.
std::ptrdiff_t difference_type
Signed distance type exposed for STL compatibility.
std::forward_iterator_tag iterator_category
Standard iterator category exposed for STL compatibility.
std::pair< NodeId, ContourRange > value_type
Pair of live node id and lazy contour range.
value_type operator*() const
Returns the current (nodeId, contourRange) pair.
iterator operator++(int)
Advances to the next live node and returns the previous iterator.
friend bool operator!=(const iterator &lhs, const iterator &rhs)
Returns true when iterators refer to different node positions.
value_type reference
Value type returned by dereference.
Lazy range over all live nodes as (nodeId, contourRange) pairs.
ContoursByNodeRange(const IncrementalContours *owner)
Creates a lazy all-node contour range backed by owner.
iterator end() const
Returns the all-node range sentinel.
iterator begin() const
Returns an iterator positioned at the first live node.
Arena-based incremental contour extraction and aggregation for MorphologicalTree.
static LocalContourDeltas extractContourDeltas(const WeightedTreeView< T > &tree)
Extracts compact local contour additions/removals from a weighted view.
static IncrementalContours extractCompactContours(const MorphologicalTree &tree)
Runs incremental contour computation and returns compact contours.
static IncrementalContours extractCompactContours(const WeightedTreeView< T > &tree)
Runs incremental contour computation on a weighted view.
detail::ContourDeltaStore LocalContourDeltas
Compact local contour additions/removals indexed by internal node id.
static constexpr double ContourSideAdjacencyRadius
Radius of the 4-neighbour side-contour adjacency used by the algorithm.
static LocalContourDeltas extractContourDeltas(const MorphologicalTree &tree)
Extracts compact local contour additions/removals without materializing aggregate contours.
Mutable morphological tree built directly on proper parts and dense node ids.
void requireMutationVersion(std::size_t expectedVersion, const char *context) const
Rejects stale read-only views that captured an older mutation version.
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 getRoot() const
Returns the current hierarchy root.
std::size_t getMutationVersion() const noexcept
Returns the monotonic mutation counter used by read-only views.
int getNumColsOfImage() const
Returns the number of image columns in the attached 2D domain.
AliveNodeRange getAliveNodeIds() const
Returns a fail-fast range over all live node ids.
int getNumRowsOfImage() const
Returns the number of image rows in the attached 2D domain.
Owning result for one computed scalar attribute layout and buffer.
Allocation and cache diagnostics for an incremental contour result.
std::size_t approxAllocatedBytes
Approximate bytes reserved by the owned vectors in this result.
std::size_t cachedContourReadyNodes
Number of live-node slots with ready materialized contours.
std::size_t removeDeltaValues
Number of compacted local contour-removal pixels.
std::size_t cachedContourCapacity
Reserved capacity of the materialized contour cache.
std::size_t addDeltaValues
Number of compacted local contour-addition pixels.
std::size_t cachedContourValues
Number of pixels already committed to materialized contour cache.
Incremental contour result stored as compact local deltas.
bool isContourMaterialized(NodeId node) const
Tests whether one live node contour is already materialized.
bool isMaterialized() const
Tests whether every live-node contour has already been materialized.
StorageStats storageStats() const
Returns allocation-oriented diagnostics for benchmarks.
ContourRange getContour(NodeId node) const
void materializeAll() const
Materializes and caches every live-node contour.
ContoursByNodeRange contoursByNode() const