MorphologicalAttributeFilters
Public API documentation
Loading...
Searching...
No Matches
ResidualEvolution.hpp
Go to the documentation of this file.
1#pragma once
2
8#include "../../utils/Altitude.hpp"
9#include "../../utils/Common.hpp"
10
11#include <cstddef>
12#include <limits>
13#include <set>
14#include <span>
15#include <stdexcept>
16#include <utility>
17#include <vector>
18
19namespace mmcfilters::sdrt {
20
22enum class Polarity { Maximum, Minimum };
23
32 public:
38 explicit SpatialOrder(std::vector<PixelId> pixelsInOrder)
39 : rankByPixel_(pixelsInOrder.size(), invalidRank()) {
40 for (std::size_t rank = 0; rank < pixelsInOrder.size(); ++rank) {
41 const PixelId pixel = pixelsInOrder[rank];
42 if (pixel < 0 || static_cast<std::size_t>(pixel) >= pixelsInOrder.size()) {
43 throw std::invalid_argument("A spatial order must be a permutation of the pixel domain.");
44 }
45 auto& storedRank = rankByPixel_[static_cast<std::size_t>(pixel)];
46 if (storedRank != invalidRank()) {
47 throw std::invalid_argument("A spatial order cannot contain a pixel more than once.");
48 }
49 storedRank = rank;
50 }
51 }
52
57 [[nodiscard]] bool isRowMajor() const noexcept { return rankByPixel_.empty(); }
58
63 void validateDomainSize(std::size_t numPixels) const {
64 if (!isRowMajor() && rankByPixel_.size() != numPixels) {
65 throw std::invalid_argument("The explicit spatial order size differs from the image domain.");
66 }
67 }
68
76 return rank(lhs) < rank(rhs);
77 }
78
84 [[nodiscard]] PixelId spatialMinimum(std::span<const PixelId> support) const {
85 if (support.empty()) {
86 throw std::invalid_argument("A spatial minimum requires a non-empty support.");
87 }
88 PixelId minimum = support.front();
89 for (PixelId pixel : support.subspan(1)) {
90 if (precedes(pixel, minimum)) {
91 minimum = pixel;
92 }
93 }
94 return minimum;
95 }
96
97 protected:
99 SpatialOrder() = default;
100
101 private:
103 [[nodiscard]] static constexpr std::size_t invalidRank() noexcept {
104 return std::numeric_limits<std::size_t>::max();
105 }
106
112 [[nodiscard]] std::size_t rank(PixelId pixel) const {
113 if (pixel < 0) {
114 throw std::out_of_range("A spatial-order query received a negative pixel identifier.");
115 }
116 if (isRowMajor()) {
117 return static_cast<std::size_t>(pixel);
118 }
119 if (static_cast<std::size_t>(pixel) >= rankByPixel_.size()) {
120 throw std::out_of_range("A spatial-order query lies outside the configured domain.");
121 }
122 return rankByPixel_[static_cast<std::size_t>(pixel)];
123 }
124
125 std::vector<std::size_t> rankByPixel_;
126};
127
130 public:
131 RowMajorSpatialOrder() = default;
132};
133
142
145 public:
152
160 if (lhs.supportCardinality != rhs.supportCardinality) {
161 return lhs.supportCardinality < rhs.supportCardinality;
162 }
163 if (lhs.spatialMinimum == rhs.spatialMinimum) {
164 return false;
165 }
166 return spatialOrder_.precedes(lhs.spatialMinimum, rhs.spatialMinimum);
167 }
168
178
183 [[nodiscard]] const SpatialOrder& spatialOrder() const noexcept { return spatialOrder_; }
184
185 private:
186 SpatialOrder spatialOrder_;
187};
188
191 public:
197 : residualOrder_(std::move(spatialOrder)) {}
198
204 [[nodiscard]] std::size_t selectResidualCandidate(std::span<const SelfDualResidualKey> residualKeys) const {
205 if (residualKeys.empty()) {
206 throw std::invalid_argument("Residual-candidate selection requires a non-empty schedule.");
207 }
208 std::set<std::pair<std::size_t, PixelId>> seenKeys;
209 std::size_t selected = 0;
210 for (std::size_t index = 0; index < residualKeys.size(); ++index) {
211 if (!seenKeys.emplace(residualKeys[index].supportCardinality, residualKeys[index].spatialMinimum).second) {
212 throw std::invalid_argument("A self-dual residual schedule cannot contain duplicate candidate keys.");
213 }
214 if (index != 0 && residualOrder_.compareResidualCandidates(residualKeys[index], residualKeys[selected])) {
215 selected = index;
216 }
217 }
218 return selected;
219 }
220
225 [[nodiscard]] const SelfDualResidualOrder& residualOrder() const noexcept { return residualOrder_; }
226
227 private:
228 SelfDualResidualOrder residualOrder_;
229};
230
232template <AltitudeValue T> struct ResidualCandidate {
233 std::span<const PixelId> support;
234 Polarity polarity = Polarity::Maximum;
238};
239
241template <AltitudeValue T> struct ResidualEvent {
242 std::size_t eventIndex = 0;
243 std::span<const PixelId> support;
244 Polarity polarity = Polarity::Maximum;
248};
249
256template <AltitudeValue T>
258 if (candidate.support.empty()) {
259 throw std::invalid_argument("A residual event requires a non-empty candidate support.");
260 }
261 if (candidate.residualKey.supportCardinality != candidate.support.size()) {
262 throw std::invalid_argument("A residual candidate key has an inconsistent support cardinality.");
263 }
264 const auto signedResidualValue = static_cast<AltitudeDifference<T>>(candidate.candidateAltitude) -
265 static_cast<AltitudeDifference<T>>(candidate.firstMergingLevel);
266 if ((candidate.polarity == Polarity::Maximum && signedResidualValue <= AltitudeDifference<T>{}) ||
267 (candidate.polarity == Polarity::Minimum && signedResidualValue >= AltitudeDifference<T>{})) {
268 throw std::invalid_argument("A residual candidate polarity is inconsistent with its signed residual value.");
269 }
270 return ResidualEvent<T>{eventIndex, candidate.support, candidate.polarity, candidate.candidateAltitude,
271 candidate.firstMergingLevel, signedResidualValue};
272}
273
274} // namespace mmcfilters::sdrt
constexpr PixelId InvalidPixel
Sentinel value used to denote an invalid pixel identifier.
Definition Common.hpp:43
ResidualEvent< T > recordResidualEvent(std::size_t eventIndex, const ResidualCandidate< T > &candidate)
Records the immutable event associated with one prepared candidate.
Polarity
Polarity of a regional extremum in synchronized residual evolution.
Dense row-major spatial order used by default.
Strict order induced by (supportCardinality, spatialMinimum).
const SpatialOrder & spatialOrder() const noexcept
Returns the total spatial order used by this comparison.
SelfDualResidualOrder(SpatialOrder spatialOrder=RowMajorSpatialOrder{})
Creates the canonical residual-key order.
bool compareResidualCandidates(const SelfDualResidualKey &lhs, const SelfDualResidualKey &rhs) const
Compares two candidate keys without consulting polarity or altitude.
bool operator()(const SelfDualResidualKey &lhs, const SelfDualResidualKey &rhs) const
Applies the canonical strict comparison.
Selects the next residual candidate from the canonical self-dual order.
std::size_t selectResidualCandidate(std::span< const SelfDualResidualKey > residualKeys) const
Returns the index of the first key in canonical residual order.
const SelfDualResidualOrder & residualOrder() const noexcept
Returns the canonical key order used by this schedule.
SelfDualResidualSchedule(SpatialOrder spatialOrder=RowMajorSpatialOrder{})
Creates the unique canonical residual schedule.
Total order over the pixels of one construction domain.
bool isRowMajor() const noexcept
Returns whether the order uses implicit row-major ranks.
SpatialOrder()=default
Creates the implicit row-major order.
void validateDomainSize(std::size_t numPixels) const
Validates that this order covers a construction domain.
bool precedes(PixelId lhs, PixelId rhs) const
Returns whether lhs precedes rhs.
SpatialOrder(std::vector< PixelId > pixelsInOrder)
Creates an explicit total spatial order.
PixelId spatialMinimum(std::span< const PixelId > support) const
Returns the least pixel of a non-empty support.
Owning result for one computed scalar attribute layout and buffer.
std::vector< Real > second
Flat per-node attribute buffer indexed through first.
Immutable view of an eligible residual candidate before leveling.
std::span< const PixelId > support
Current candidate support.
Polarity polarity
Regional-extremum polarity.
T firstMergingLevel
First adjacent level reached by elementary leveling.
SelfDualResidualKey residualKey
Canonical support-only scheduling key.
T candidateAltitude
Constant altitude on the candidate before leveling.
Immutable record captured immediately before one elementary leveling.
T nodeAltitude
Candidate altitude before leveling.
Polarity polarity
Polarity in the pre-leveling state.
std::size_t eventIndex
Zero-based chronological event index.
AltitudeDifference< T > signedResidualValue
nodeAltitude - firstMergingLevel.
std::span< const PixelId > support
Candidate support in the pre-leveling state.
T firstMergingLevel
Level to which the support is moved.
Contrast-invariant key of one current residual candidate.
PixelId spatialMinimum
Least support pixel in the configured spatial order.
friend bool operator==(const SelfDualResidualKey &, const SelfDualResidualKey &)=default
Compares both coordinates of two residual keys.
std::size_t supportCardinality
Cardinality of the current candidate support.