mmcfilters
Public API documentation
Loading...
Searching...
No Matches
DynamicTreeAttributeComputer.hpp
1#pragma once
2
3#include "../ValuedMorphologicalTree.hpp"
4#include "../../utils/Image.hpp"
5
6#include <algorithm>
7#include <cmath>
8#include <utility>
9#include <vector>
10
11namespace mmcfilters::adjust {
12
20enum class BoundingBoxMeasure {
22 Width,
24 Height,
26 DiagonalLength
27};
28
50template <AltitudeValue T> class DynamicTreeAttributeComputer {
51 public:
53 using buffer_type = std::vector<double>;
54
58 virtual ~DynamicTreeAttributeComputer() = default;
59
71 virtual void resize(const ValuedMorphologicalTree<T>& tree, buffer_type& buffer) const {
72 buffer.resize(static_cast<size_t>(tree.topology().numInternalNodeSlots()), 0.0);
73 }
74
83
94
103
114
124
133 virtual void onNodeRemoved(NodeId, const ValuedMorphologicalTree<T>&) const {}
134
146 resize(tree, buffer);
147 const MorphologicalTree& topology = tree.topology();
148 for (NodeId nodeId : topology.postOrder()) {
150 for (NodeId childId : topology.children(nodeId)) {
152 }
154 }
155 }
156
166 for (NodeId childId : tree.topology().children(nodeId)) {
168 }
170 }
171};
172
183template <AltitudeValue T> class DynamicAreaAttributeComputer : public DynamicTreeAttributeComputer<T> {
184 private:
187
188 public:
191
192 public:
201 buffer[static_cast<size_t>(nodeId)] = static_cast<double>(tree.topology().properPartCardinality(nodeId));
202 }
203
212 buffer[static_cast<size_t>(parentId)] += buffer[static_cast<size_t>(childId)];
213 }
214
219};
220
238template <AltitudeValue T> class DynamicBoundingBoxAttributeComputer : public DynamicTreeAttributeComputer<T> {
239 private:
242
243 public:
246
247 private:
251 struct BoxState {
253 int xmin = 0;
255 int xmax = -1;
257 int ymin = 0;
259 int ymax = -1;
261 bool empty = true;
262 };
263
272 struct LocalBoxState {
274 int xmin = 0;
276 int xmax = -1;
278 int ymin = 0;
280 int ymax = -1;
282 int xminCount = 0;
284 int xmaxCount = 0;
286 int yminCount = 0;
288 int ymaxCount = 0;
290 int properPartCount = 0;
292 bool empty = true;
294 bool dirty = true;
295 };
296
298 BoundingBoxMeasure measure_ = BoundingBoxMeasure::DiagonalLength;
300 mutable std::vector<LocalBoxState> local_;
302 mutable std::vector<BoxState> subtree_;
303
309 static void resetLocalBox(LocalBoxState& local) {
310 local.xmin = 0;
311 local.xmax = -1;
312 local.ymin = 0;
313 local.ymax = -1;
314 local.xminCount = 0;
315 local.xmaxCount = 0;
316 local.yminCount = 0;
317 local.ymaxCount = 0;
318 local.properPartCount = 0;
319 local.empty = true;
320 }
321
327 void resetLocalSummary(NodeId nodeId) const {
328 auto& local = local_[static_cast<size_t>(nodeId)];
329 resetLocalBox(local);
330 local.dirty = false;
331 }
332
338 void resetSubtreeSummary(NodeId nodeId) const {
339 auto& subtree = subtree_[static_cast<size_t>(nodeId)];
340 subtree.xmin = 0;
341 subtree.xmax = -1;
342 subtree.ymin = 0;
343 subtree.ymax = -1;
344 subtree.empty = true;
345 }
346
354 void expandLocalBoxWithPixel(LocalBoxState& local, PixelId pixelId, int numColumns) const {
355 const auto [y, x] = ImageUtils::to2D(pixelId, numColumns);
356 if (local.empty) {
357 local.xmin = x;
358 local.xmax = x;
359 local.ymin = y;
360 local.ymax = y;
361 local.xminCount = 1;
362 local.xmaxCount = 1;
363 local.yminCount = 1;
364 local.ymaxCount = 1;
365 local.empty = false;
366 return;
367 }
368
369 if (x < local.xmin) {
370 local.xmin = x;
371 local.xminCount = 1;
372 } else if (x == local.xmin) {
373 ++local.xminCount;
374 }
375
376 if (x > local.xmax) {
377 local.xmax = x;
378 local.xmaxCount = 1;
379 } else if (x == local.xmax) {
380 ++local.xmaxCount;
381 }
382
383 if (y < local.ymin) {
384 local.ymin = y;
385 local.yminCount = 1;
386 } else if (y == local.ymin) {
387 ++local.yminCount;
388 }
389
390 if (y > local.ymax) {
391 local.ymax = y;
392 local.ymaxCount = 1;
393 } else if (y == local.ymax) {
394 ++local.ymaxCount;
395 }
396 }
397
404 void rebuildLocalBox(NodeId nodeId, const ValuedMorphologicalTree<T>& tree) const {
405 auto& local = local_[static_cast<size_t>(nodeId)];
406 resetLocalBox(local);
407 const int numColumns = tree.topology().numColumns();
408 for (PixelId pixelId : tree.topology().properPart(nodeId)) {
409 expandLocalBoxWithPixel(local, pixelId, numColumns);
410 }
411 local.properPartCount = tree.topology().properPartCardinality(nodeId);
412 local.dirty = false;
413 }
414
424 void ensureLocalSummary(NodeId nodeId, const ValuedMorphologicalTree<T>& tree) const {
425 auto& local = local_[static_cast<size_t>(nodeId)];
426 const int properPartCount = tree.topology().properPartCardinality(nodeId);
427 if (!local.dirty && local.properPartCount == properPartCount) {
428 return;
429 }
430 rebuildLocalBox(nodeId, tree);
431 }
432
438 void copyLocalToSubtree(NodeId nodeId) const {
439 const auto& local = local_[static_cast<size_t>(nodeId)];
440 auto& subtree = subtree_[static_cast<size_t>(nodeId)];
441 subtree.xmin = local.xmin;
442 subtree.xmax = local.xmax;
443 subtree.ymin = local.ymin;
444 subtree.ymax = local.ymax;
445 subtree.empty = local.empty;
446 }
447
454 static void mergeSubtreeStates(BoxState& target, const BoxState& source) {
455 if (source.empty) {
456 return;
457 }
458 if (target.empty) {
459 target = source;
460 return;
461 }
462 target.xmin = std::min(target.xmin, source.xmin);
463 target.xmax = std::max(target.xmax, source.xmax);
464 target.ymin = std::min(target.ymin, source.ymin);
465 target.ymax = std::max(target.ymax, source.ymax);
466 target.empty = false;
467 }
468
475 static void mergeLocalBoxes(LocalBoxState& target, const LocalBoxState& source) {
476 if (source.empty) {
477 return;
478 }
479 if (target.empty) {
480 target = source;
481 return;
482 }
483
484 if (source.xmin < target.xmin) {
485 target.xmin = source.xmin;
486 target.xminCount = source.xminCount;
487 } else if (source.xmin == target.xmin) {
488 target.xminCount += source.xminCount;
489 }
490
491 if (source.xmax > target.xmax) {
492 target.xmax = source.xmax;
493 target.xmaxCount = source.xmaxCount;
494 } else if (source.xmax == target.xmax) {
495 target.xmaxCount += source.xmaxCount;
496 }
497
498 if (source.ymin < target.ymin) {
499 target.ymin = source.ymin;
500 target.yminCount = source.yminCount;
501 } else if (source.ymin == target.ymin) {
502 target.yminCount += source.yminCount;
503 }
504
505 if (source.ymax > target.ymax) {
506 target.ymax = source.ymax;
507 target.ymaxCount = source.ymaxCount;
508 } else if (source.ymax == target.ymax) {
509 target.ymaxCount += source.ymaxCount;
510 }
511
512 target.properPartCount += source.properPartCount;
513 target.empty = false;
514 target.dirty = false;
515 }
516
517 public:
524 explicit DynamicBoundingBoxAttributeComputer(BoundingBoxMeasure measure = BoundingBoxMeasure::DiagonalLength) : measure_(measure) {}
525
535 void resize(const ValuedMorphologicalTree<T>& tree, buffer_type& buffer) const override {
536 base_t::resize(tree, buffer);
537 const size_t size = static_cast<size_t>(tree.topology().numInternalNodeSlots());
538 local_.resize(size);
539 subtree_.resize(size);
540 }
541
549 ensureLocalSummary(nodeId, tree);
550 copyLocalToSubtree(nodeId);
551 }
552
560 mergeSubtreeStates(subtree_[static_cast<size_t>(parentId)], subtree_[static_cast<size_t>(childId)]);
561 }
562
570 const auto& subtree = subtree_[static_cast<size_t>(nodeId)];
571 if (subtree.empty) {
572 buffer[static_cast<size_t>(nodeId)] = 0.0;
573 return;
574 }
575
576 const double width = static_cast<double>(subtree.xmax - subtree.xmin + 1);
577 const double height = static_cast<double>(subtree.ymax - subtree.ymin + 1);
578 switch (measure_) {
579 case BoundingBoxMeasure::Width:
580 buffer[static_cast<size_t>(nodeId)] = width;
581 break;
582 case BoundingBoxMeasure::Height:
583 buffer[static_cast<size_t>(nodeId)] = height;
584 break;
585 case BoundingBoxMeasure::DiagonalLength:
586 buffer[static_cast<size_t>(nodeId)] = std::sqrt(width * width + height * height);
587 break;
588 }
589 }
590
599 ensureLocalSummary(targetId, tree);
600 ensureLocalSummary(sourceId, tree);
601 mergeLocalBoxes(local_[static_cast<size_t>(targetId)], local_[static_cast<size_t>(sourceId)]);
602 resetLocalSummary(sourceId);
603 }
604
617 ensureLocalSummary(targetId, tree);
618 if (sourceId != InvalidNode) {
619 ensureLocalSummary(sourceId, tree);
620 }
621
622 const int numColumns = tree.topology().numColumns();
623 expandLocalBoxWithPixel(local_[static_cast<size_t>(targetId)], pixelId, numColumns);
624 local_[static_cast<size_t>(targetId)].properPartCount += 1;
625 local_[static_cast<size_t>(targetId)].dirty = false;
626
627 if (sourceId == InvalidNode) {
628 return;
629 }
630
631 auto& source = local_[static_cast<size_t>(sourceId)];
632 if (source.properPartCount <= 1) {
633 resetLocalSummary(sourceId);
634 return;
635 }
636
637 const auto [y, x] = ImageUtils::to2D(pixelId, numColumns);
638 --source.properPartCount;
639
640 bool exhaustsXmin = false;
641 bool exhaustsXmax = false;
642 bool exhaustsYmin = false;
643 bool exhaustsYmax = false;
644
645 if (!source.dirty) {
646 if (x == source.xmin) {
647 if (source.xminCount <= 1) {
648 exhaustsXmin = true;
649 } else {
650 --source.xminCount;
651 }
652 }
653 if (x == source.xmax) {
654 if (source.xmaxCount <= 1) {
655 exhaustsXmax = true;
656 } else {
657 --source.xmaxCount;
658 }
659 }
660 if (y == source.ymin) {
661 if (source.yminCount <= 1) {
662 exhaustsYmin = true;
663 } else {
664 --source.yminCount;
665 }
666 }
667 if (y == source.ymax) {
668 if (source.ymaxCount <= 1) {
669 exhaustsYmax = true;
670 } else {
671 --source.ymaxCount;
672 }
673 }
674 }
675
676 source.dirty = source.dirty || exhaustsXmin || exhaustsXmax || exhaustsYmin || exhaustsYmax;
677 }
678
685 resetLocalSummary(nodeId);
686 resetSubtreeSummary(nodeId);
687 }
688};
689
690} // namespace mmcfilters::adjust
int PixelId
Pixel identifier type used by source and active construction domains.
Definition Common.hpp:26
int NodeId
Node identifier type used throughout the project.
Definition Common.hpp:17
constexpr NodeId InvalidNode
Sentinel value used to denote an invalid node identifier.
Definition Common.hpp:34
static std::pair< int, int > to2D(PixelId index, int numColumns) noexcept
Converts a row-major linear index to (row, column).
Definition Image.hpp:312
Mutable connected-subset tree on a finite pixel domain.
PostOrderNodeRange postOrder() const
Returns a post-order traversal range rooted at the connected root.
ChildrenRange children(NodeId nodeId) const
Returns a fail-fast range over the direct children of nodeId.
void postProcessing(NodeId, const ValuedMorphologicalTree< T > &, buffer_type &) const override
Area has no finalization step beyond child accumulation.
void preProcessing(NodeId nodeId, const ValuedMorphologicalTree< T > &tree, buffer_type &buffer) const override
Initializes one node area from its direct proper-part count.
void mergeProcessing(NodeId parentId, NodeId childId, const ValuedMorphologicalTree< T > &, buffer_type &buffer) const override
Adds an already-current child area to its parent.
typename base_t::buffer_type buffer_type
Dense per-node area buffer inherited from the dynamic attribute protocol.
void onMoveProperParts(NodeId targetId, NodeId sourceId, const ValuedMorphologicalTree< T > &tree) const override
Updates local boxes after all proper parts move from sourceId to targetId.
void resize(const ValuedMorphologicalTree< T > &tree, buffer_type &buffer) const override
Resizes public and auxiliary buffers to the current tree slot space.
void onMoveProperPart(NodeId targetId, NodeId sourceId, PixelId pixelId, const ValuedMorphologicalTree< T > &tree) const override
Updates local boxes after one proper part moves between nodes.
void mergeProcessing(NodeId parentId, NodeId childId, const ValuedMorphologicalTree< T > &, buffer_type &) const override
Accumulates a child subtree box into its parent subtree box.
void postProcessing(NodeId nodeId, const ValuedMorphologicalTree< T > &, buffer_type &buffer) const override
Converts the accumulated subtree box into the configured scalar measure.
void preProcessing(NodeId nodeId, const ValuedMorphologicalTree< T > &tree, buffer_type &) const override
Initializes one node's subtree bounding box from its proper part.
void onNodeRemoved(NodeId nodeId, const ValuedMorphologicalTree< T > &) const override
Clears auxiliary summaries associated with a released node slot.
DynamicBoundingBoxAttributeComputer(BoundingBoxMeasure measure=BoundingBoxMeasure::DiagonalLength)
Creates a bounding-box computer returning the requested scalar measure.
typename base_t::buffer_type buffer_type
Dense per-node bounding-box attribute buffer inherited from the dynamic protocol.
Common protocol for attributes maintained during local tree adjustment.
virtual void mergeProcessing(NodeId parentId, NodeId childId, const ValuedMorphologicalTree< T > &tree, buffer_type &buffer) const =0
Accumulates an already-current child contribution into its parent.
void computeAttribute(const ValuedMorphologicalTree< T > &tree, buffer_type &buffer) const
Computes the attribute for the full current tree in post-order.
virtual ~DynamicTreeAttributeComputer()=default
Destroys a dynamic attribute computer through the protocol base.
virtual void postProcessing(NodeId nodeId, const ValuedMorphologicalTree< T > &tree, buffer_type &buffer) const =0
Materializes the final scalar value for one node after all child merges.
virtual void resize(const ValuedMorphologicalTree< T > &tree, buffer_type &buffer) const
Resizes an attribute buffer to the full internal node-id space.
virtual void onMoveProperParts(NodeId, NodeId, const ValuedMorphologicalTree< T > &) const
Incremental hook called after all pixels in one node's proper part move to another node.
virtual void preProcessing(NodeId nodeId, const ValuedMorphologicalTree< T > &tree, buffer_type &buffer) const =0
Initializes the direct contribution of one node before child merges.
virtual void onMoveProperPart(NodeId, NodeId, PixelId, const ValuedMorphologicalTree< T > &) const
Incremental hook called after one pixel moves between node proper parts.
std::vector< double > buffer_type
Dense per-node attribute buffer used by dynamic adjustment computers.
void computeAttributeOnNode(const ValuedMorphologicalTree< T > &tree, NodeId nodeId, buffer_type &buffer) const
Recomputes one node assuming all direct children are already up to date.
virtual void onNodeRemoved(NodeId, const ValuedMorphologicalTree< T > &) const
Incremental hook called when one node slot is released from the live tree.
Owning result for one computed scalar attribute layout and buffer.