MorphologicalAttributeFilters
Public API documentation
Loading...
Searching...
No Matches
DynamicTreeAttributeComputer.hpp
1#pragma once
2
3#include "../WeightedMorphologicalTree.hpp"
4
5#include <algorithm>
6#include <cmath>
7#include <utility>
8#include <vector>
9
10namespace mmcfilters::adjust {
11
19enum class BoundingBoxMeasure {
21 WIDTH,
23 HEIGHT,
25 DIAGONAL_LENGTH
26};
27
49template<AltitudeValue T>
51public:
53 using buffer_type = std::vector<double>;
54
58 virtual ~DynamicTreeAttributeComputer() = default;
59
71 virtual void resize(const WeightedMorphologicalTree<T>& tree, buffer_type& buffer) const {
72 buffer.resize(static_cast<size_t>(tree.topology().getNumInternalNodeSlots()), 0.0);
73 }
74
83
94
103
114
124
133 virtual void onNodeRemoved(NodeId, const WeightedMorphologicalTree<T>&) const {}
134
146 resize(tree, buffer);
147 const MorphologicalTree& topology = tree.topology();
148 for (NodeId nodeId : topology.getPostOrderNodes()) {
150 for (NodeId childId : topology.getChildren(nodeId)) {
152 }
154 }
155 }
156
166 for (NodeId childId : tree.topology().getChildren(nodeId)) {
168 }
170 }
171};
172
183template<AltitudeValue T>
185private:
187
188public:
191
192public:
197 buffer[static_cast<size_t>(nodeId)] = static_cast<double>(tree.topology().getNumProperParts(nodeId));
198 }
199
204 buffer[static_cast<size_t>(parentId)] += buffer[static_cast<size_t>(childId)];
205 }
206
211};
212
230template<AltitudeValue T>
232private:
234
235public:
238
239private:
243 struct BoxState {
244 int xmin = 0;
245 int xmax = -1;
246 int ymin = 0;
247 int ymax = -1;
248 bool empty = true;
249 };
250
259 struct LocalBoxState {
260 int xmin = 0;
261 int xmax = -1;
262 int ymin = 0;
263 int ymax = -1;
264 int xminCount = 0;
265 int xmaxCount = 0;
266 int yminCount = 0;
267 int ymaxCount = 0;
268 int properPartCount = 0;
269 bool empty = true;
270 bool dirty = true;
271 };
272
273 BoundingBoxMeasure measure_ = BoundingBoxMeasure::DIAGONAL_LENGTH;
274 mutable std::vector<LocalBoxState> local_;
275 mutable std::vector<BoxState> subtree_;
276
280 static void resetLocalBox(LocalBoxState& local) {
281 local.xmin = 0;
282 local.xmax = -1;
283 local.ymin = 0;
284 local.ymax = -1;
285 local.xminCount = 0;
286 local.xmaxCount = 0;
287 local.yminCount = 0;
288 local.ymaxCount = 0;
289 local.properPartCount = 0;
290 local.empty = true;
291 }
292
296 void resetLocalSummary(NodeId nodeId) const {
297 auto& local = local_[static_cast<size_t>(nodeId)];
298 resetLocalBox(local);
299 local.dirty = false;
300 }
301
305 void resetSubtreeSummary(NodeId nodeId) const {
306 auto& subtree = subtree_[static_cast<size_t>(nodeId)];
307 subtree.xmin = 0;
308 subtree.xmax = -1;
309 subtree.ymin = 0;
310 subtree.ymax = -1;
311 subtree.empty = true;
312 }
313
317 void expandLocalBoxWithPixel(LocalBoxState& local, NodeId pixelId, int numCols) const {
318 const auto [y, x] = ImageUtils::to2D(pixelId, numCols);
319 if (local.empty) {
320 local.xmin = x;
321 local.xmax = x;
322 local.ymin = y;
323 local.ymax = y;
324 local.xminCount = 1;
325 local.xmaxCount = 1;
326 local.yminCount = 1;
327 local.ymaxCount = 1;
328 local.empty = false;
329 return;
330 }
331
332 if (x < local.xmin) {
333 local.xmin = x;
334 local.xminCount = 1;
335 } else if (x == local.xmin) {
336 ++local.xminCount;
337 }
338
339 if (x > local.xmax) {
340 local.xmax = x;
341 local.xmaxCount = 1;
342 } else if (x == local.xmax) {
343 ++local.xmaxCount;
344 }
345
346 if (y < local.ymin) {
347 local.ymin = y;
348 local.yminCount = 1;
349 } else if (y == local.ymin) {
350 ++local.yminCount;
351 }
352
353 if (y > local.ymax) {
354 local.ymax = y;
355 local.ymaxCount = 1;
356 } else if (y == local.ymax) {
357 ++local.ymaxCount;
358 }
359 }
360
364 void rebuildLocalBox(NodeId nodeId, const WeightedMorphologicalTree<T>& tree) const {
365 auto& local = local_[static_cast<size_t>(nodeId)];
366 resetLocalBox(local);
367 const int numCols = tree.topology().getNumColsOfImage();
368 for (NodeId pixelId : tree.topology().getProperParts(nodeId)) {
369 expandLocalBoxWithPixel(local, pixelId, numCols);
370 }
371 local.properPartCount = tree.topology().getNumProperParts(nodeId);
372 local.dirty = false;
373 }
374
381 void ensureLocalSummary(NodeId nodeId, const WeightedMorphologicalTree<T>& tree) const {
382 auto& local = local_[static_cast<size_t>(nodeId)];
383 const int properPartCount = tree.topology().getNumProperParts(nodeId);
384 if (!local.dirty && local.properPartCount == properPartCount) {
385 return;
386 }
387 rebuildLocalBox(nodeId, tree);
388 }
389
393 void copyLocalToSubtree(NodeId nodeId) const {
394 const auto& local = local_[static_cast<size_t>(nodeId)];
395 auto& subtree = subtree_[static_cast<size_t>(nodeId)];
396 subtree.xmin = local.xmin;
397 subtree.xmax = local.xmax;
398 subtree.ymin = local.ymin;
399 subtree.ymax = local.ymax;
400 subtree.empty = local.empty;
401 }
402
406 static void mergeSubtreeStates(BoxState& target, const BoxState& source) {
407 if (source.empty) {
408 return;
409 }
410 if (target.empty) {
411 target = source;
412 return;
413 }
414 target.xmin = std::min(target.xmin, source.xmin);
415 target.xmax = std::max(target.xmax, source.xmax);
416 target.ymin = std::min(target.ymin, source.ymin);
417 target.ymax = std::max(target.ymax, source.ymax);
418 target.empty = false;
419 }
420
424 static void mergeLocalBoxes(LocalBoxState& target, const LocalBoxState& source) {
425 if (source.empty) {
426 return;
427 }
428 if (target.empty) {
429 target = source;
430 return;
431 }
432
433 if (source.xmin < target.xmin) {
434 target.xmin = source.xmin;
435 target.xminCount = source.xminCount;
436 } else if (source.xmin == target.xmin) {
437 target.xminCount += source.xminCount;
438 }
439
440 if (source.xmax > target.xmax) {
441 target.xmax = source.xmax;
442 target.xmaxCount = source.xmaxCount;
443 } else if (source.xmax == target.xmax) {
444 target.xmaxCount += source.xmaxCount;
445 }
446
447 if (source.ymin < target.ymin) {
448 target.ymin = source.ymin;
449 target.yminCount = source.yminCount;
450 } else if (source.ymin == target.ymin) {
451 target.yminCount += source.yminCount;
452 }
453
454 if (source.ymax > target.ymax) {
455 target.ymax = source.ymax;
456 target.ymaxCount = source.ymaxCount;
457 } else if (source.ymax == target.ymax) {
458 target.ymaxCount += source.ymaxCount;
459 }
460
461 target.properPartCount += source.properPartCount;
462 target.empty = false;
463 target.dirty = false;
464 }
465
466public:
473 explicit DynamicBoundingBoxAttributeComputer(BoundingBoxMeasure measure = BoundingBoxMeasure::DIAGONAL_LENGTH)
474 : measure_(measure) {}
475
482 void resize(const WeightedMorphologicalTree<T>& tree, buffer_type& buffer) const override {
483 base_t::resize(tree, buffer);
484 const size_t size = static_cast<size_t>(tree.topology().getNumInternalNodeSlots());
485 local_.resize(size);
486 subtree_.resize(size);
487 }
488
493 ensureLocalSummary(nodeId, tree);
494 copyLocalToSubtree(nodeId);
495 }
496
501 mergeSubtreeStates(subtree_[static_cast<size_t>(parentId)], subtree_[static_cast<size_t>(childId)]);
502 }
503
508 const auto& subtree = subtree_[static_cast<size_t>(nodeId)];
509 if (subtree.empty) {
510 buffer[static_cast<size_t>(nodeId)] = 0.0;
511 return;
512 }
513
514 const double width = static_cast<double>(subtree.xmax - subtree.xmin + 1);
515 const double height = static_cast<double>(subtree.ymax - subtree.ymin + 1);
516 switch (measure_) {
517 case BoundingBoxMeasure::WIDTH:
518 buffer[static_cast<size_t>(nodeId)] = width;
519 break;
520 case BoundingBoxMeasure::HEIGHT:
521 buffer[static_cast<size_t>(nodeId)] = height;
522 break;
523 case BoundingBoxMeasure::DIAGONAL_LENGTH:
524 buffer[static_cast<size_t>(nodeId)] = std::sqrt(width * width + height * height);
525 break;
526 }
527 }
528
533 ensureLocalSummary(targetId, tree);
534 ensureLocalSummary(sourceId, tree);
535 mergeLocalBoxes(local_[static_cast<size_t>(targetId)], local_[static_cast<size_t>(sourceId)]);
536 resetLocalSummary(sourceId);
537 }
538
546 ensureLocalSummary(targetId, tree);
547 if (sourceId != InvalidNode) {
548 ensureLocalSummary(sourceId, tree);
549 }
550
551 const int numCols = tree.topology().getNumColsOfImage();
552 expandLocalBoxWithPixel(local_[static_cast<size_t>(targetId)], pixelId, numCols);
553 local_[static_cast<size_t>(targetId)].properPartCount += 1;
554 local_[static_cast<size_t>(targetId)].dirty = false;
555
556 if (sourceId == InvalidNode) {
557 return;
558 }
559
560 auto& source = local_[static_cast<size_t>(sourceId)];
561 if (source.properPartCount <= 1) {
562 resetLocalSummary(sourceId);
563 return;
564 }
565
566 const auto [y, x] = ImageUtils::to2D(pixelId, numCols);
567 --source.properPartCount;
568
569 bool exhaustsXmin = false;
570 bool exhaustsXmax = false;
571 bool exhaustsYmin = false;
572 bool exhaustsYmax = false;
573
574 if (!source.dirty) {
575 if (x == source.xmin) {
576 if (source.xminCount <= 1) {
577 exhaustsXmin = true;
578 } else {
579 --source.xminCount;
580 }
581 }
582 if (x == source.xmax) {
583 if (source.xmaxCount <= 1) {
584 exhaustsXmax = true;
585 } else {
586 --source.xmaxCount;
587 }
588 }
589 if (y == source.ymin) {
590 if (source.yminCount <= 1) {
591 exhaustsYmin = true;
592 } else {
593 --source.yminCount;
594 }
595 }
596 if (y == source.ymax) {
597 if (source.ymaxCount <= 1) {
598 exhaustsYmax = true;
599 } else {
600 --source.ymaxCount;
601 }
602 }
603 }
604
606 }
607
612 resetLocalSummary(nodeId);
613 resetSubtreeSummary(nodeId);
614 }
615};
616
617
618} // namespace mmcfilters::adjust
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:25
static std::pair< int, int > to2D(int index, int numCols) noexcept
Converts a row-major linear index to (row, col).
Definition Image.hpp:283
Mutable morphological tree built directly on proper parts and dense node ids.
ChildrenRange getChildren(NodeId nodeId) const
Returns a fail-fast range over the direct children of nodeId.
PostOrderNodeRange getPostOrderNodes() const
Returns a post-order traversal range rooted at the connected root.
void preProcessing(NodeId nodeId, const WeightedMorphologicalTree< T > &tree, buffer_type &buffer) const override
Initializes one node area from its direct proper-part count.
void mergeProcessing(NodeId parentId, NodeId childId, const WeightedMorphologicalTree< T > &, buffer_type &buffer) const override
Adds an already-current child area to its parent.
void postProcessing(NodeId, const WeightedMorphologicalTree< T > &, buffer_type &) const override
Area has no finalization step beyond child accumulation.
typename base_t::buffer_type buffer_type
Dense per-node area buffer inherited from the dynamic attribute protocol.
void postProcessing(NodeId nodeId, const WeightedMorphologicalTree< T > &, buffer_type &buffer) const override
Converts the accumulated subtree box into the configured scalar measure.
void preProcessing(NodeId nodeId, const WeightedMorphologicalTree< T > &tree, buffer_type &) const override
Initializes the subtree box of one node from its direct proper parts.
void onMoveProperPart(NodeId targetId, NodeId sourceId, NodeId pixelId, const WeightedMorphologicalTree< T > &tree) const override
Updates local boxes after one proper part moves between nodes.
void resize(const WeightedMorphologicalTree< T > &tree, buffer_type &buffer) const override
Resizes public and auxiliary buffers to the current tree slot space.
DynamicBoundingBoxAttributeComputer(BoundingBoxMeasure measure=BoundingBoxMeasure::DIAGONAL_LENGTH)
Creates a bounding-box computer returning the requested scalar measure.
void mergeProcessing(NodeId parentId, NodeId childId, const WeightedMorphologicalTree< T > &, buffer_type &) const override
Accumulates a child subtree box into its parent subtree box.
void onNodeRemoved(NodeId nodeId, const WeightedMorphologicalTree< T > &) const override
Clears auxiliary summaries associated with a released node slot.
typename base_t::buffer_type buffer_type
Dense per-node bounding-box attribute buffer inherited from the dynamic protocol.
void onMoveProperParts(NodeId targetId, NodeId sourceId, const WeightedMorphologicalTree< T > &tree) const override
Updates local boxes after all proper parts move from sourceId to targetId.
Common protocol for attributes maintained during local tree adjustment.
virtual void mergeProcessing(NodeId parentId, NodeId childId, const WeightedMorphologicalTree< T > &tree, buffer_type &buffer) const =0
Accumulates an already-current child contribution into its parent.
virtual void resize(const WeightedMorphologicalTree< T > &tree, buffer_type &buffer) const
Resizes an attribute buffer to the full internal node-id space.
virtual void onMoveProperParts(NodeId, NodeId, const WeightedMorphologicalTree< T > &) const
Incremental hook called after all direct proper parts move from one node to another.
virtual ~DynamicTreeAttributeComputer()=default
Destroys a dynamic attribute computer through the protocol base.
virtual void preProcessing(NodeId nodeId, const WeightedMorphologicalTree< T > &tree, buffer_type &buffer) const =0
Initializes the direct contribution of one node before child merges.
void computeAttribute(const WeightedMorphologicalTree< T > &tree, buffer_type &buffer) const
Computes the attribute for the full current tree in post-order.
virtual void postProcessing(NodeId nodeId, const WeightedMorphologicalTree< T > &tree, buffer_type &buffer) const =0
Materializes the final scalar value for one node after all child merges.
virtual void onNodeRemoved(NodeId, const WeightedMorphologicalTree< T > &) const
Incremental hook called when one node slot is released from the live tree.
std::vector< double > buffer_type
Dense per-node attribute buffer used by dynamic adjustment computers.
void computeAttributeOnNode(const WeightedMorphologicalTree< T > &tree, NodeId nodeId, buffer_type &buffer) const
Recomputes one node assuming all direct children are already up to date.
virtual void onMoveProperPart(NodeId, NodeId, NodeId, const WeightedMorphologicalTree< T > &) const
Incremental hook called after one proper part moves between nodes.
Owning result for one computed scalar attribute layout and buffer.