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"
11#include "detail/maxdist/EdtDIFT.hpp"
26namespace mmcfilters::attributes::computers::detail {
42class MaxDistAttributeKernel {
50 static void requireSupportedTreeKind(
const MorphologicalTree& tree)
52 switch (tree.getTreeType()) {
53 case MorphologicalTreeKind::MAX_TREE:
54 case MorphologicalTreeKind::MIN_TREE:
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.");
64 throw std::invalid_argument(
"MAX_DIST received an unsupported morphological tree kind.");
73 template<std::
floating_po
int Real, AltitudeValue T>
74 [[nodiscard]]
static std::vector<Real> compute(
75 const MorphologicalTree& tree,
76 std::span<const T> altitude)
78 requireSupportedTreeKind(tree);
79 if (!tree.hasAdjacencyRelation()) {
80 throw std::invalid_argument(
"MAX_DIST requires an adjacency relation.");
83 validateFiniteAltitude(altitude);
85 std::vector<Real> maxDist(
static_cast<std::size_t
>(tree.getNumInternalNodeSlots()), Real{0});
86 maxdist::EdtDIFT edtDIFT(tree.getNumRowsOfImage(), tree.getNumColsOfImage());
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);
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])) {
107 std::span<const NodeId>(
108 sortedNodes.data() +
static_cast<std::ptrdiff_t
>(groupBegin),
109 groupEnd - groupBegin),
117 groupBegin = groupEnd;
129 template<AltitudeValue T>
130 static bool isFiniteAltitude(T value)
noexcept
132 if constexpr (std::is_floating_point_v<T>) {
133 return std::isfinite(value);
141 template<AltitudeValue T>
142 static void validateFiniteAltitude(std::span<const T> altitude)
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.");
153 static void markPixels(std::span<const int> pixels, std::vector<std::uint8_t>& marks)
155 for (
int pixelId : pixels) {
156 marks[
static_cast<std::size_t
>(pixelId)] = 1;
160 static void clearPixelMarks(std::span<const int> pixels, std::vector<std::uint8_t>& marks)
162 for (
int pixelId : pixels) {
163 marks[
static_cast<std::size_t
>(pixelId)] = 0;
176 template<AltitudeValue T>
177 [[nodiscard]]
static std::vector<NodeId> sortedNodesByAltitude(
178 const MorphologicalTree& tree,
179 std::span<const T> altitude)
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);
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;
201 template<AltitudeValue T>
202 static bool sameAltitude(
203 std::span<const T> altitude,
221 template <std::
floating_po
int 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)
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)];
242 const auto removals = contourDeltas.removals(nodeId);
244 toRemove.insert(toRemove.end(), removals.begin(), removals.end());
245 markPixels(removals, removalMark);
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();
252 nodeContour.reserve(reserveSize);
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);
261 std::vector<int>().swap(childContour);
263 clearPixelMarks(removals, removalMark);
265 if (!toRemove.empty()) {
266 edtDIFT.treeRemoval(toRemove);
269 markPixels(additions, contourAdditionMark);
270 for (
int pixelId : tree.getProperParts(nodeId)) {
271 edtDIFT.addPixelToBinaryImage(pixelId);
273 if (contourAdditionMark[
static_cast<std::size_t
>(pixelId)]) {
274 nodeContour.push_back(pixelId);
275 edtDIFT.seed(pixelId);
278 edtDIFT.open(pixelId);
279 edtDIFT.insertNeighborsPQueue(pixelId);
282 clearPixelMarks(additions, contourAdditionMark);
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)]));
299namespace mmcfilters::attributes::computers {
310class MaxDistComputer {
313 static constexpr std::string_view familyName =
"max-dist";
316 static constexpr AttributeComputerFamily family = AttributeComputerFamily::MaxDist;
319 static constexpr AttributeComputerDomain domain = AttributeComputerDomain::Altitude;
324 inline static constexpr std::array<Attribute, 1> producedAttributes{MAX_DIST};
329 static void requireSupportedTreeKind(
const MorphologicalTree& tree)
331 detail::MaxDistAttributeKernel::requireSupportedTreeKind(tree);
341 template<std::
floating_po
int Real, AltitudeValue T>
342 static void compute(
const AltitudeAttributeComputeContext<Real, T>& context)
344 requireAttributeBufferShape(context.tree, context.buffer, context.attrNames);
345 if (!requestsAttribute(context.requestedAttributes, MAX_DIST)) {
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)];
363 template <std::
floating_po
int Real, AltitudeValue T>
364 static void computeUnitRows(
const AltitudeUnitAttributeComputeContext<Real, T>& context)
366 requireUnitAttributeBufferShape(context.tree, context.unitProperParts, context.buffer, context.attrNames);
368 if (!requestsAttribute(context.requestedAttributes, MAX_DIST)) {
372 for (NodeId leafIndex = 0; leafIndex < static_cast<NodeId>(context.unitProperParts.size()); ++leafIndex) {
373 context.buffer[context.attrNames.linearIndex(leafIndex, MAX_DIST)] = Real{0};
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.