MorphologicalAttributeFilters
Public API documentation
Loading...
Searching...
No Matches
MaxDistComputer.hpp
1#pragma once
2
3#include "AttributeComputerDomain.hpp"
4#include "AttributeComputerFamily.hpp"
5#include "../detail/AttributeKernelSupport.hpp"
6#include "../../contours/ContoursComputedIncrementally.hpp"
7#include "../../trees/MorphologicalTree.hpp"
8#include "../../trees/TreeAltitudeAlgorithms.hpp"
9#include "../../utils/Common.hpp"
10
11#include "detail/maxdist/EdtDIFT.hpp"
12
13#include <algorithm>
14#include <array>
15#include <cmath>
16#include <concepts>
17#include <cstddef>
18#include <cstdint>
19#include <span>
20#include <stdexcept>
21#include <string>
22#include <string_view>
23#include <type_traits>
24#include <vector>
25
26namespace mmcfilters::attributes::computers::detail {
27
42class MaxDistAttributeKernel {
43public:
50 static void requireSupportedTreeKind(const MorphologicalTree& tree)
51 {
52 switch (tree.getTreeType()) {
53 case MorphologicalTreeKind::MAX_TREE:
54 case MorphologicalTreeKind::MIN_TREE:
55 return;
56 case MorphologicalTreeKind::TREE_OF_SHAPES:
57 throw std::invalid_argument(
58 "MAX_DIST is currently defined only for MAX_TREE and MIN_TREE; TREE_OF_SHAPES is not supported.");
59 case MorphologicalTreeKind::SELF_DUAL_RESIDUAL_TREE:
60 throw std::invalid_argument(
61 "MAX_DIST is currently defined only for MAX_TREE and MIN_TREE; SELF_DUAL_RESIDUAL_TREE is not supported.");
62 }
63
64 throw std::invalid_argument("MAX_DIST received an unsupported morphological tree kind.");
65 }
66
73 template<std::floating_point Real, AltitudeValue T>
74 [[nodiscard]] static std::vector<Real> compute(
75 const MorphologicalTree& tree,
76 std::span<const T> altitude)
77 {
78 requireSupportedTreeKind(tree);
79 if (!tree.hasAdjacencyRelation()) {
80 throw std::invalid_argument("MAX_DIST requires an adjacency relation.");
81 }
83 validateFiniteAltitude(altitude);
84
85 std::vector<Real> maxDist(static_cast<std::size_t>(tree.getNumInternalNodeSlots()), Real{0});
86 maxdist::EdtDIFT edtDIFT(tree.getNumRowsOfImage(), tree.getNumColsOfImage());
87
88 const ContourDeltaStore contourDeltas =
90 std::vector<std::vector<int>> contours(static_cast<std::size_t>(tree.getNumInternalNodeSlots()));
91 const std::size_t totalPixels = static_cast<std::size_t>(
92 tree.getNumRowsOfImage() * tree.getNumColsOfImage());
93 std::vector<std::uint8_t> removalMark(totalPixels, 0);
94 std::vector<std::uint8_t> contourAdditionMark(totalPixels, 0);
95
96 std::vector<NodeId> sortedNodes = sortedNodesByAltitude(tree, altitude);
97 std::size_t groupBegin = 0;
98 while (groupBegin < sortedNodes.size()) {
99 std::size_t groupEnd = groupBegin + 1;
100 while (groupEnd < sortedNodes.size() &&
101 sameAltitude(altitude, sortedNodes[groupBegin], sortedNodes[groupEnd])) {
102 ++groupEnd;
103 }
104
105 processLevel(
106 tree,
107 std::span<const NodeId>(
108 sortedNodes.data() + static_cast<std::ptrdiff_t>(groupBegin),
109 groupEnd - groupBegin),
110 contourDeltas,
111 edtDIFT,
112 contours,
113 removalMark,
114 contourAdditionMark,
115 maxDist);
116
117 groupBegin = groupEnd;
118 }
119
120 return maxDist;
121 }
122
123private:
125
129 template<AltitudeValue T>
130 static bool isFiniteAltitude(T value) noexcept
131 {
132 if constexpr (std::is_floating_point_v<T>) {
133 return std::isfinite(value);
134 }
135 return true;
136 }
137
141 template<AltitudeValue T>
142 static void validateFiniteAltitude(std::span<const T> altitude)
143 {
144 for (std::size_t index = 0; index < altitude.size(); ++index) {
145 if (!isFiniteAltitude(altitude[index])) {
146 throw std::invalid_argument(
147 "MAX_DIST requires finite altitude values; node " +
148 std::to_string(index) + " has a non-finite altitude.");
149 }
150 }
151 }
152
153 static void markPixels(std::span<const int> pixels, std::vector<std::uint8_t>& marks)
154 {
155 for (int pixelId : pixels) {
156 marks[static_cast<std::size_t>(pixelId)] = 1;
157 }
158 }
159
160 static void clearPixelMarks(std::span<const int> pixels, std::vector<std::uint8_t>& marks)
161 {
162 for (int pixelId : pixels) {
163 marks[static_cast<std::size_t>(pixelId)] = 0;
164 }
165 }
166
176 template<AltitudeValue T>
177 [[nodiscard]] static std::vector<NodeId> sortedNodesByAltitude(
178 const MorphologicalTree& tree,
179 std::span<const T> altitude)
180 {
181 std::vector<NodeId> nodes;
182 nodes.reserve(static_cast<std::size_t>(tree.getNumNodes()));
183 for (NodeId nodeId : tree.getPostOrderNodes()) {
184 nodes.push_back(nodeId);
185 }
186
187 std::stable_sort(nodes.begin(), nodes.end(), [&](NodeId lhs, NodeId rhs) {
188 const T lhsAltitude = TreeAltitudeAlgorithms::getAltitude(altitude, lhs);
189 const T rhsAltitude = TreeAltitudeAlgorithms::getAltitude(altitude, rhs);
190 return tree.getTreeType() == MorphologicalTreeKind::MAX_TREE
191 ? lhsAltitude > rhsAltitude
192 : lhsAltitude < rhsAltitude;
193 });
194
195 return nodes;
196 }
197
201 template<AltitudeValue T>
202 static bool sameAltitude(
203 std::span<const T> altitude,
204 NodeId lhs,
205 NodeId rhs)
206 {
207 return TreeAltitudeAlgorithms::getAltitude(altitude, lhs) ==
209 }
210
221 template <std::floating_point Real>
222 static void processLevel(
223 const MorphologicalTree& tree,
224 std::span<const NodeId> nodes,
225 const ContourDeltaStore& contourDeltas,
226 maxdist::EdtDIFT& edtDIFT,
227 std::vector<std::vector<int>>& contours,
228 std::vector<std::uint8_t>& removalMark,
229 std::vector<std::uint8_t>& contourAdditionMark,
230 std::vector<Real>& maxDist)
231 {
232 if (nodes.empty()) {
233 return;
234 }
235
236 std::vector<int> toRemove;
237 toRemove.reserve(64);
238 for (NodeId nodeId : nodes) {
239 std::vector<int>& nodeContour = contours[static_cast<std::size_t>(nodeId)];
240 nodeContour.clear();
241
242 const auto removals = contourDeltas.removals(nodeId);
243 toRemove.clear();
244 toRemove.insert(toRemove.end(), removals.begin(), removals.end());
245 markPixels(removals, removalMark);
246
247 const auto additions = contourDeltas.additions(nodeId);
248 std::size_t reserveSize = additions.size();
249 for (NodeId childNodeId : tree.getChildren(nodeId)) {
250 reserveSize += contours[static_cast<std::size_t>(childNodeId)].size();
251 }
252 nodeContour.reserve(reserveSize);
253
254 for (NodeId childNodeId : tree.getChildren(nodeId)) {
255 std::vector<int>& childContour = contours[static_cast<std::size_t>(childNodeId)];
256 for (int pixelId : childContour) {
257 if (!removalMark[static_cast<std::size_t>(pixelId)]) {
258 nodeContour.push_back(pixelId);
259 }
260 }
261 std::vector<int>().swap(childContour);
262 }
263 clearPixelMarks(removals, removalMark);
264
265 if (!toRemove.empty()) {
266 edtDIFT.treeRemoval(toRemove);
267 }
268
269 markPixels(additions, contourAdditionMark);
270 for (int pixelId : tree.getProperParts(nodeId)) {
271 edtDIFT.addPixelToBinaryImage(pixelId);
272
273 if (contourAdditionMark[static_cast<std::size_t>(pixelId)]) {
274 nodeContour.push_back(pixelId);
275 edtDIFT.seed(pixelId);
276 }
277 else {
278 edtDIFT.open(pixelId);
279 edtDIFT.insertNeighborsPQueue(pixelId);
280 }
281 }
282 clearPixelMarks(additions, contourAdditionMark);
283 }
284
285 // All nodes at the same altitude must enter the binary image before the
286 // distance transform propagates. This preserves the mathematical
287 // per-level schedule without requiring fixed 0..255 buckets.
288 edtDIFT.run();
289
290 for (NodeId nodeId : nodes) {
291 maxDist[static_cast<std::size_t>(nodeId)] =
292 static_cast<Real>(edtDIFT.maxBedt(contours[static_cast<std::size_t>(nodeId)]));
293 }
294 }
295};
296
297} // namespace mmcfilters::attributes::computers::detail
298
299namespace mmcfilters::attributes::computers {
300
310class MaxDistComputer {
311public:
313 static constexpr std::string_view familyName = "max-dist";
314
316 static constexpr AttributeComputerFamily family = AttributeComputerFamily::MaxDist;
317
319 static constexpr AttributeComputerDomain domain = AttributeComputerDomain::Altitude;
320
324 inline static constexpr std::array<Attribute, 1> producedAttributes{MAX_DIST};
325
329 static void requireSupportedTreeKind(const MorphologicalTree& tree)
330 {
331 detail::MaxDistAttributeKernel::requireSupportedTreeKind(tree);
332 }
333
341 template<std::floating_point Real, AltitudeValue T>
342 static void compute(const AltitudeAttributeComputeContext<Real, T>& context)
343 {
344 requireAttributeBufferShape(context.tree, context.buffer, context.attrNames);
345 if (!requestsAttribute(context.requestedAttributes, MAX_DIST)) {
346 return;
347 }
348
349 const std::vector<Real> maxDist =
350 detail::MaxDistAttributeKernel::compute<Real>(context.tree, context.altitude);
351 for (NodeId nodeId : context.tree.getAliveNodeIds()) {
352 context.buffer[context.attrNames.linearIndex(nodeId, MAX_DIST)] =
353 maxDist[static_cast<std::size_t>(nodeId)];
354 }
355 }
356
363 template <std::floating_point Real, AltitudeValue T>
364 static void computeUnitRows(const AltitudeUnitAttributeComputeContext<Real, T>& context)
365 {
366 requireUnitAttributeBufferShape(context.tree, context.unitProperParts, context.buffer, context.attrNames);
367 TreeAltitudeAlgorithms::validateAltitudeBufferShape(context.tree, context.altitude);
368 if (!requestsAttribute(context.requestedAttributes, MAX_DIST)) {
369 return;
370 }
371
372 for (NodeId leafIndex = 0; leafIndex < static_cast<NodeId>(context.unitProperParts.size()); ++leafIndex) {
373 context.buffer[context.attrNames.linearIndex(leafIndex, MAX_DIST)] = Real{0};
374 }
375 }
376};
377
378} // namespace mmcfilters::attributes::computers
detail::ContourDeltaStore LocalContourDeltas
Compact local contour additions/removals indexed by internal node id.
static LocalContourDeltas extractContourDeltas(const MorphologicalTree &tree)
Extracts compact local contour additions/removals without materializing aggregate contours.
static T getAltitude(std::span< const T > altitude, NodeId nodeId)
Reads one node altitude from an explicit altitude buffer.
static void validateAltitudeBufferShape(const MorphologicalTree &tree, std::span< const T > altitude)
Validates that an altitude buffer covers the dense internal-node domain.