mmcfilters
Public API documentation
Loading...
Searching...
No Matches
UltimateAttributeOpening.hpp
1#pragma once
2
3#include "../utils/Image.hpp"
4#include "../utils/Common.hpp"
5#include "../trees/ValuedMorphologicalTree.hpp"
6#include "../trees/ValuedMorphologicalTreeView.hpp"
7#include "../trees/detail/CommittedTreeAccess.hpp"
8#include "../utils/Contract.hpp"
9#include "DepthStableRegionComputer.hpp"
10#include "MSERComputer.hpp"
11
12#include <cmath>
13#include <concepts>
14#include <memory>
15#include <stack>
16#include <stdexcept>
17#include <string>
18#include <vector>
19
20namespace mmcfilters {
21
38template <AltitudeValue T, std::floating_point Real = float> class UltimateAttributeOpening {
39 public:
42
43 protected:
45
47
49 Real maximumAttributeThreshold = Real{0};
51 const Real* const attrs_increasing;
53 std::shared_ptr<Real[]> ownedAttrsIncreasing_;
55 const ValuedMorphologicalTree<T>* valuedTree_ = nullptr;
59 const MorphologicalTree& tree;
61 std::size_t treeMutationVersion_ = 0;
63 std::vector<T> maxContrastLUT;
65 std::vector<int> associatedIndexLUT;
67 std::vector<uint8_t> selectedForFiltering;
68
74 AltitudeView view() const { return valuedTree_ != nullptr ? valuedTree_->asView() : view_; }
75
81 void requireStableTree(const char* context) const { tree.requireMutationVersion(treeMutationVersion_, context); }
82
90 static T altitudeOf(const AltitudeView& view, NodeId nodeId) noexcept {
91 return view.nodeAltitudes()[static_cast<std::size_t>(nodeId)];
92 }
93
100 static void requireAttributePointer(const Real* attr, const char* context) {
101 MMCFILTERS_CONTRACT_REQUIRE(attr != nullptr,
102 throw std::invalid_argument(std::string(context) + " requires a non-null attribute buffer."));
103 }
104
113 static const Real* requireAttributeBuffer(const MorphologicalTree& tree, const std::vector<Real>& attr, const char* context) {
114 MMCFILTERS_CONTRACT_REQUIRE(attr.size() == static_cast<std::size_t>(tree.numInternalNodeSlots()),
115 throw std::invalid_argument(std::string(context) + " attribute size must match the internal node slot count."));
116 return attr.data();
117 }
118
126 static T absoluteAltitudeDifference(AltitudeDifference<T> lhs, AltitudeDifference<T> rhs) {
127 const long double difference = std::abs(static_cast<long double>(lhs) - static_cast<long double>(rhs));
128 // UAO stores the contrast in the same type as the altitude by API
129 // decision. For signed integral altitudes, contrasts that do not fit in
130 // T can lose information here; avoiding that requires a separate
131 // contrast type, which this API intentionally does not introduce.
132 return static_cast<T>(difference);
133 }
134
144 void computeUAO(const AltitudeView& view, NodeId currentNodeId, AltitudeDifference<T> altitudeNodeNotInNR, bool qPropag, bool isCalculateResidue) {
145 const NodeId parentNodeId = detail::CommittedTreeAccess::nodeParent(tree, currentNodeId);
146 const AltitudeDifference<T> altitudeNodeInNR = static_cast<AltitudeDifference<T>>(altitudeOf(view, currentNodeId));
147 bool flagPropag = false;
148 T contrast = T{};
149
150 if (this->isSelectedForPruning(currentNodeId)) {
151 altitudeNodeNotInNR = static_cast<AltitudeDifference<T>>(altitudeOf(view, parentNodeId));
152 if (this->attrs_increasing[currentNodeId] <= this->maximumAttributeThreshold) {
153 isCalculateResidue = hasNodeSelectedInPrimitive(currentNodeId);
154 }
155 }
156
157 if (this->attrs_increasing[currentNodeId] <= this->maximumAttributeThreshold) {
158 if (isCalculateResidue) {
159 contrast = absoluteAltitudeDifference(altitudeNodeInNR, altitudeNodeNotInNR);
160 }
161
162 if (this->maxContrastLUT[parentNodeId] >= contrast) {
163 this->maxContrastLUT[currentNodeId] = this->maxContrastLUT[parentNodeId];
164 this->associatedIndexLUT[currentNodeId] = this->associatedIndexLUT[parentNodeId];
165 } else {
166 this->maxContrastLUT[currentNodeId] = contrast;
167 this->associatedIndexLUT[currentNodeId] =
168 !qPropag ? static_cast<int>(this->attrs_increasing[currentNodeId] + 1) : this->associatedIndexLUT[parentNodeId];
169 flagPropag = true;
170 }
171 }
172
173 for (NodeId childNodeId : detail::CommittedTreeAccess::children(tree, currentNodeId)) {
174 this->computeUAO(view, childNodeId, altitudeNodeNotInNR, flagPropag, isCalculateResidue);
175 }
176 }
177
184 void executeImpl(Real maximumAttributeThreshold, const std::vector<uint8_t>& selectedForFiltering) {
185 const AltitudeView altitudeView = view();
186 this->maximumAttributeThreshold = maximumAttributeThreshold;
187 this->selectedForFiltering = selectedForFiltering;
188
189 for (NodeId id : tree.aliveNodeIds()) {
190 maxContrastLUT[id] = T{};
191 associatedIndexLUT[id] = 0;
192 }
193
194 const NodeId rootNodeId = tree.root();
195 const AltitudeDifference<T> level = static_cast<AltitudeDifference<T>>(altitudeOf(altitudeView, rootNodeId));
196 for (NodeId childNodeId : detail::CommittedTreeAccess::children(tree, rootNodeId)) {
197 computeUAO(altitudeView, childNodeId, level, false, false);
198 }
199 }
200
207 bool isSelectedForPruning(NodeId currentNodeId) const {
208 const NodeId parentNodeId = detail::CommittedTreeAccess::nodeParent(tree, currentNodeId);
209 if (parentNodeId == InvalidNode) {
210 return false;
211 }
212 return this->attrs_increasing[currentNodeId] != this->attrs_increasing[parentNodeId];
213 }
214
221 bool hasNodeSelectedInPrimitive(NodeId currentNodeId) const {
222 std::stack<NodeId> stack;
223 stack.push(currentNodeId);
224 while (!stack.empty()) {
225 const NodeId nodeId = stack.top();
226 stack.pop();
227 if (selectedForFiltering[nodeId]) {
228 return true;
229 }
230
231 for (NodeId childNodeId : detail::CommittedTreeAccess::children(tree, nodeId)) {
232 if (this->attrs_increasing[childNodeId] == this->attrs_increasing[nodeId]) {
233 stack.push(childNodeId);
234 }
235 }
236 }
237 return false;
238 }
240
241 public:
250 UltimateAttributeOpening(const AltitudeView& view, const std::shared_ptr<Real[]>& attrs_increasing)
252 this->ownedAttrsIncreasing_ = attrs_increasing;
253 }
254
265 UltimateAttributeOpening(const AltitudeView& view, const std::vector<Real>& attrs_increasing)
266 : UltimateAttributeOpening(view, requireAttributeBuffer(view.topology(), attrs_increasing, "UltimateAttributeOpening")) {}
267
279 : attrs_increasing(attrs_increasing), view_(view), tree(view_.topology()), treeMutationVersion_(tree.getMutationVersion()),
280 maxContrastLUT(this->tree.numInternalNodeSlots()), associatedIndexLUT(this->tree.numInternalNodeSlots()) {
281 view_.requireTopologyUnchanged("UltimateAttributeOpening");
282 requireAttributePointer(attrs_increasing, "UltimateAttributeOpening");
283 this->selectedForFiltering.assign(this->tree.numInternalNodeSlots(), true);
284 }
285
298 this->ownedAttrsIncreasing_ = attrs_increasing;
299 }
300
317
332
337
338 public:
347 requireStableTree("UltimateAttributeOpening::execute");
348 std::vector<uint8_t> tmp(this->tree.numInternalNodeSlots(), true);
350 }
351
363 void execute(Real maximumAttributeThreshold, const std::vector<uint8_t>& selectedForFiltering) {
364 requireStableTree("UltimateAttributeOpening::execute");
366 selectedForFiltering.size() == static_cast<std::size_t>(this->tree.numInternalNodeSlots()),
367 throw std::invalid_argument("UltimateAttributeOpening::execute selectedForFiltering size must match the internal node slot count."));
369 }
370
382 requireStableTree("UltimateAttributeOpening::executeWithMSER");
383 if (valuedTree_ == nullptr) {
384 throw std::logic_error(
385 "UltimateAttributeOpening::executeWithMSER requires a ValuedMorphologicalTree owner because MSER uses the tree-owned altitude.");
386 }
387 MSERComputer<T, Real> mser(*valuedTree_);
389 }
390
402 requireStableTree("UltimateAttributeOpening::executeWithDepthStability");
405 }
406
414 requireStableTree("UltimateAttributeOpening::getMaxContrastImage");
415 const int size = this->tree.numColumns() * this->tree.numRows();
416 ImagePtr<T> imgOut = Image<T>::create(this->tree.numRows(), this->tree.numColumns());
417 auto out = imgOut->rawData();
418
419 for (int pidx = 0; pidx < size; pidx++) {
420 out[pidx] = this->maxContrastLUT[detail::CommittedTreeAccess::smallestNodeMap(tree, pidx)];
421 }
422 return imgOut;
423 }
424
432 requireStableTree("UltimateAttributeOpening::getAssociatedImage");
433 const int size = this->tree.numColumns() * this->tree.numRows();
434 ImageInt32Ptr imgOut = ImageInt32::create(this->tree.numRows(), this->tree.numColumns());
435 auto out = imgOut->rawData();
436
437 for (int pidx = 0; pidx < size; pidx++) {
438 out[pidx] = this->associatedIndexLUT[detail::CommittedTreeAccess::smallestNodeMap(tree, pidx)];
439 }
440 return imgOut;
441 }
442
450 return ImageUtils::createRandomColor(this->getAssociatedImage()->rawData(), this->tree.numRows(),
451 this->tree.numColumns());
452 }
453};
454
455} // namespace mmcfilters
int NodeId
Node identifier type used throughout the project.
Definition Common.hpp:17
#define MMCFILTERS_CONTRACT_REQUIRE(condition,...)
Evaluates a caller precondition and its failure action only in checked builds.
Definition Contract.hpp:53
std::shared_ptr< ImageInt32 > ImageInt32Ptr
Shared pointer to a 32-bit signed integer image.
Definition Image.hpp:278
std::shared_ptr< ImageUInt8 > ImageUInt8Ptr
Shared pointer to an 8-bit unsigned image.
Definition Image.hpp:276
static ImageUInt8Ptr createRandomColor(int *img, int numRowsOfImage, int numColumnsOfImage)
Creates a random-colour visualisation from an integer-labelled image.
Definition Image.hpp:331
static Ptr create(int rows, int columns)
Creates an owned image with uninitialised pixel values.
Definition Image.hpp:105
Mutable connected-subset tree on a finite pixel domain.
void requireMutationVersion(std::size_t expectedVersion, const char *context) const
Rejects stale read-only views that captured an older mutation version.
Computes an Ultimate Attribute Opening by accumulating maximal contrasts.
UltimateAttributeOpening(const ValuedMorphologicalTree< T > &valuedTree, const std::shared_ptr< Real[]> &attrs_increasing)
Creates a UAO computation over a borrowed valued tree.
void executeWithDepthStability(Real maximumAttributeThreshold, int depthWindowRadius)
Executes UAO with a depth-stability node-selection mask.
ImagePtr< T > getMaxContrastImage() const
Returns the per-pixel maximum UAO contrast image.
UltimateAttributeOpening(const AltitudeView &view, const Real *attrs_increasing)
Creates a UAO computation over a non-owning valued-tree view.
UltimateAttributeOpening(const ValuedMorphologicalTree< T > &valuedTree, const Real *attrs_increasing)
Creates a UAO computation over a borrowed valued tree.
void executeWithMSER(Real maximumAttributeThreshold, AltitudeDifference< T > altitudeWindowRadius)
Executes UAO with an MSER-derived node-selection mask.
UltimateAttributeOpening(const AltitudeView &view, const std::shared_ptr< Real[]> &attrs_increasing)
Creates a UAO computation over a non-owning valued-tree view.
void execute(Real maximumAttributeThreshold, const std::vector< uint8_t > &selectedForFiltering)
Executes UAO with an explicit node-selection mask.
UltimateAttributeOpening(const AltitudeView &view, const std::vector< Real > &attrs_increasing)
Creates a UAO computation over a non-owning valued-tree view.
Real attribute_value_type
Floating-point type used for the input attribute buffer and thresholds.
UltimateAttributeOpening(const ValuedMorphologicalTree< T > &valuedTree, const std::vector< Real > &attrs_increasing)
Creates a UAO computation over a borrowed valued tree.
ImageUInt8Ptr getAssociatedColorImage() const
Returns a color rendering of the associated-index image.
void execute(Real maximumAttributeThreshold)
Executes UAO using all internal tree nodes as selectable candidates.
~UltimateAttributeOpening()=default
Destroys the ultimate-attribute-opening evaluator.
ImageInt32Ptr getAssociatedImage() const
Returns the per-pixel associated attribute-index image.
Owning result for one computed scalar attribute layout and buffer.