MorphologicalAttributeFilters
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/WeightedMorphologicalTree.hpp"
6#include "../trees/WeightedTreeView.hpp"
7#include "DepthStableRegionComputer.hpp"
8#include "MSERComputer.hpp"
9
10#include <cmath>
11#include <concepts>
12#include <memory>
13#include <stack>
14#include <stdexcept>
15#include <string>
16#include <vector>
17
18namespace mmcfilters {
19
36template<AltitudeValue T, std::floating_point Real = float>
38public:
41
42protected:
45
47 const Real* const attrs_increasing;
48 std::shared_ptr<Real[]> ownedAttrsIncreasing_;
49 const WeightedMorphologicalTree<T>* weighted_ = nullptr;
51 const MorphologicalTree& tree;
52 std::size_t treeMutationVersion_ = 0;
53 std::vector<T> maxContrastLUT;
54 std::vector<int> associatedIndexLUT;
55 std::vector<uint8_t> selectedForFiltering;
56
57 AltitudeView view() const {
58 return weighted_ != nullptr ? weighted_->asView() : view_;
59 }
60
61 void requireStableTree(const char* context) const {
62 tree.requireMutationVersion(treeMutationVersion_, context);
63 }
64
65 static T altitudeOf(const AltitudeView& view, NodeId nodeId) {
66 return view.getAltitude(nodeId);
67 }
68
69 static void requireAttributePointer(const Real* attr, const char* context) {
70 if (attr == nullptr) {
71 throw std::invalid_argument(std::string(context) + " requires a non-null attribute buffer.");
72 }
73 }
74
75 static const Real* requireAttributeBuffer(const MorphologicalTree& tree, const std::vector<Real>& attr, const char* context) {
76 if (attr.size() != static_cast<std::size_t>(tree.getNumInternalNodeSlots())) {
77 throw std::invalid_argument(std::string(context) + " attribute size must match the internal node slot count.");
78 }
79 return attr.data();
80 }
81
82 static T absoluteAltitudeDifference(AltitudeDiff<T> lhs, AltitudeDiff<T> rhs) {
83 const long double difference = std::abs(static_cast<long double>(lhs) - static_cast<long double>(rhs));
84 // UAO stores the contrast in the same type as the altitude by API
85 // decision. For signed integral altitudes, contrasts that do not fit in
86 // T can lose information here; avoiding that requires a separate
87 // contrast type, which this API intentionally does not introduce.
88 return static_cast<T>(difference);
89 }
90
91 void computeUAO(const AltitudeView& view, NodeId currentNodeId, AltitudeDiff<T> altitudeNodeNotInNR, bool qPropag, bool isCalculateResidue) {
92 const NodeId parentNodeId = tree.getNodeParent(currentNodeId);
93 const AltitudeDiff<T> altitudeNodeInNR = static_cast<AltitudeDiff<T>>(altitudeOf(view, currentNodeId));
94 bool flagPropag = false;
95 T contrast = T{};
96
97 if (this->isSelectedForPruning(currentNodeId)) {
98 altitudeNodeNotInNR = static_cast<AltitudeDiff<T>>(altitudeOf(view, parentNodeId));
99 if (this->attrs_increasing[currentNodeId] <= this->maxCriterion) {
100 isCalculateResidue = hasNodeSelectedInPrimitive(currentNodeId);
101 }
102 }
103
104 if (this->attrs_increasing[currentNodeId] <= this->maxCriterion) {
105 if (isCalculateResidue) {
106 contrast = absoluteAltitudeDifference(altitudeNodeInNR, altitudeNodeNotInNR);
107 }
108
109 if (this->maxContrastLUT[parentNodeId] >= contrast) {
110 this->maxContrastLUT[currentNodeId] = this->maxContrastLUT[parentNodeId];
111 this->associatedIndexLUT[currentNodeId] = this->associatedIndexLUT[parentNodeId];
112 } else {
113 this->maxContrastLUT[currentNodeId] = contrast;
114 this->associatedIndexLUT[currentNodeId] = !qPropag
115 ? static_cast<int>(this->attrs_increasing[currentNodeId] + 1)
116 : this->associatedIndexLUT[parentNodeId];
117 flagPropag = true;
118 }
119 }
120
121 for (NodeId childNodeId : tree.getChildren(currentNodeId)) {
122 this->computeUAO(view, childNodeId, altitudeNodeNotInNR, flagPropag, isCalculateResidue);
123 }
124 }
125
126 void executeImpl(Real maxCriterion, const std::vector<uint8_t>& selectedForFiltering) {
127 const AltitudeView altitudeView = view();
128 this->maxCriterion = maxCriterion;
129 this->selectedForFiltering = selectedForFiltering;
130
131 for (NodeId id : tree.getAliveNodeIds()) {
132 maxContrastLUT[id] = T{};
133 associatedIndexLUT[id] = 0;
134 }
135
136 const NodeId rootNodeId = tree.getRoot();
137 const AltitudeDiff<T> level = static_cast<AltitudeDiff<T>>(altitudeOf(altitudeView, rootNodeId));
138 for (NodeId childNodeId : tree.getChildren(rootNodeId)) {
139 computeUAO(altitudeView, childNodeId, level, false, false);
140 }
141 }
142
143 bool isSelectedForPruning(NodeId currentNodeId) const {
144 const NodeId parentNodeId = tree.getNodeParent(currentNodeId);
145 if (parentNodeId == InvalidNode) {
146 return false;
147 }
148 return this->attrs_increasing[currentNodeId] != this->attrs_increasing[parentNodeId];
149 }
150
151 bool hasNodeSelectedInPrimitive(NodeId currentNodeId) const {
152 std::stack<NodeId> stack;
153 stack.push(currentNodeId);
154 while (!stack.empty()) {
155 const NodeId nodeId = stack.top();
156 stack.pop();
157 if (selectedForFiltering[nodeId]) {
158 return true;
159 }
160
161 for (NodeId childNodeId : tree.getChildren(nodeId)) {
162 if (this->attrs_increasing[childNodeId] == this->attrs_increasing[nodeId]) {
163 stack.push(childNodeId);
164 }
165 }
166 }
167 return false;
168 }
170
171public:
172
183 this->ownedAttrsIncreasing_ = attrs_increasing;
184 }
185
197 : UltimateAttributeOpening(view, requireAttributeBuffer(view.topology(), attrs_increasing, "UltimateAttributeOpening")) {}
198
211 view_(view),
212 tree(view_.topology()),
213 treeMutationVersion_(tree.getMutationVersion()),
214 maxContrastLUT(this->tree.getNumInternalNodeSlots()),
215 associatedIndexLUT(this->tree.getNumInternalNodeSlots()) {
216 view_.requireTopologyUnchanged("UltimateAttributeOpening");
217 requireAttributePointer(attrs_increasing, "UltimateAttributeOpening");
218 this->selectedForFiltering.assign(this->tree.getNumInternalNodeSlots(), true);
219 }
220
233 this->ownedAttrsIncreasing_ = attrs_increasing;
234 }
235
252
267
268 ~UltimateAttributeOpening() = default;
269
270public:
279 requireStableTree("UltimateAttributeOpening::execute");
280 std::vector<uint8_t> tmp(this->tree.getNumInternalNodeSlots(), true);
282 }
283
295 void execute(Real maxCriterion, const std::vector<uint8_t>& selectedForFiltering) {
296 requireStableTree("UltimateAttributeOpening::execute");
297 if (selectedForFiltering.size() != static_cast<std::size_t>(this->tree.getNumInternalNodeSlots())) {
298 throw std::invalid_argument("UltimateAttributeOpening::execute selectedForFiltering size must match the internal node slot count.");
299 }
301 }
302
314 {
315 requireStableTree("UltimateAttributeOpening::executeWithMSER");
316 if (weighted_ == nullptr) {
317 throw std::logic_error("UltimateAttributeOpening::executeWithMSER requires a WeightedMorphologicalTree owner because MSER uses the tree-owned altitude.");
318 }
319 MSERComputer<T, Real> mser(*weighted_);
320 executeImpl(maxCriterion, mser.computeMSER(deltaMSER));
321 }
322
334 {
335 requireStableTree("UltimateAttributeOpening::executeWithDepthStability");
338 }
339
347 requireStableTree("UltimateAttributeOpening::getMaxContrastImage");
348 const int size = this->tree.getNumColsOfImage() * this->tree.getNumRowsOfImage();
349 ImagePtr<T> imgOut = Image<T>::create(this->tree.getNumRowsOfImage(), this->tree.getNumColsOfImage());
350 auto out = imgOut->rawData();
351
352 for (int pidx = 0; pidx < size; pidx++) {
353 out[pidx] = this->maxContrastLUT[tree.getProperPartOwner(pidx)];
354 }
355 return imgOut;
356 }
357
365 requireStableTree("UltimateAttributeOpening::getAssociatedImage");
366 const int size = this->tree.getNumColsOfImage() * this->tree.getNumRowsOfImage();
367 ImageInt32Ptr imgOut = ImageInt32::create(this->tree.getNumRowsOfImage(), this->tree.getNumColsOfImage());
368 auto out = imgOut->rawData();
369
370 for (int pidx = 0; pidx < size; pidx++) {
371 out[pidx] = this->associatedIndexLUT[tree.getProperPartOwner(pidx)];
372 }
373 return imgOut;
374 }
375
383 requireStableTree("UltimateAttributeOpening::getAssociatedColorImage");
384 return ImageUtils::createRandomColor(this->getAssociatedImage()->rawData(), this->tree.getNumRowsOfImage(), this->tree.getNumColsOfImage());
385 }
386};
387
388
389} // namespace mmcfilters
int NodeId
Node identifier type used throughout the project.
Definition Common.hpp:17
std::shared_ptr< ImageInt32 > ImageInt32Ptr
Shared pointer to a 32-bit signed integer image.
Definition Image.hpp:245
std::shared_ptr< ImageUInt8 > ImageUInt8Ptr
Shared pointer to an 8-bit unsigned image.
Definition Image.hpp:243
static ImageUInt8Ptr createRandomColor(int *img, int numRowsOfImage, int numColsOfImage)
Creates a random-colour visualisation from an integer-labelled image.
Definition Image.hpp:302
static Ptr create(int rows, int cols)
Creates an owned image with uninitialised pixel values.
Definition Image.hpp:79
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.
Computes an Ultimate Attribute Opening by accumulating maximal contrasts.
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 weighted view.
void executeWithDepthStability(Real maxCriterion, int depthDelta)
Executes UAO with a depth-stability node-selection mask.
UltimateAttributeOpening(const AltitudeView &view, const std::shared_ptr< Real[]> &attrs_increasing)
Creates a UAO computation over a non-owning weighted view.
UltimateAttributeOpening(const AltitudeView &view, const std::vector< Real > &attrs_increasing)
Creates a UAO computation over a non-owning weighted view.
UltimateAttributeOpening(const WeightedMorphologicalTree< T > &weighted, const std::shared_ptr< Real[]> &attrs_increasing)
Creates a UAO computation over a borrowed weighted tree.
ImageUInt8Ptr getAssociatedColorImage() const
Returns a color rendering of the associated-index image.
UltimateAttributeOpening(const WeightedMorphologicalTree< T > &weighted, const Real *attrs_increasing)
Creates a UAO computation over a borrowed weighted tree.
UltimateAttributeOpening(const WeightedMorphologicalTree< T > &weighted, const std::vector< Real > &attrs_increasing)
Creates a UAO computation over a borrowed weighted tree.
void executeWithMSER(Real maxCriterion, AltitudeDiff< T > deltaMSER)
Executes UAO with an MSER-derived node-selection mask.
void execute(Real maxCriterion, const std::vector< uint8_t > &selectedForFiltering)
Executes UAO with an explicit node-selection mask.
void execute(Real maxCriterion)
Executes UAO using all internal tree nodes as selectable candidates.
ImageInt32Ptr getAssociatedImage() const
Returns the per-pixel associated attribute-index image.
Owning result for one computed scalar attribute layout and buffer.